claude-mem-lite 3.47.0 → 3.49.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/hook-context.mjs +20 -1
- package/hook-llm.mjs +2 -1
- package/hook-optimize.mjs +23 -13
- package/hook.mjs +54 -3
- package/lib/activity.mjs +2 -1
- package/lib/citation-tracker.mjs +7 -1
- package/lib/delete-core.mjs +72 -0
- package/lib/events-injection.mjs +100 -0
- package/lib/export-columns.mjs +4 -1
- package/lib/obs-types.mjs +12 -0
- package/lib/save-nudge.mjs +22 -0
- package/lib/search-core.mjs +47 -5
- package/lib/stats-core.mjs +113 -0
- package/mem-cli.mjs +22 -147
- package/package.json +6 -1
- package/registry.mjs +1 -1
- package/schema.mjs +1 -1
- package/scripts/pre-tool-recall.js +12 -4
- package/scripts/user-prompt-search.js +4 -1
- package/server.mjs +17 -136
- package/source-files.mjs +23 -0
- package/tool-schemas.mjs +2 -1
package/server.mjs
CHANGED
|
@@ -12,19 +12,21 @@ import { reRankWithContext, autoBoostIfNeeded, runIdleCleanup, buildServerInstru
|
|
|
12
12
|
import { searchObservationsHybrid } from './search-engine.mjs';
|
|
13
13
|
import { deepSearch, resolveDeepMode, shouldEscalateToDeep, autoDeepLlmReady } from './deep-search.mjs';
|
|
14
14
|
import { selectCompressionCandidates, groupByProjectWeek, compressGroup } from './lib/compress-core.mjs';
|
|
15
|
-
import { buildNotLowSignalSql } from './lib/low-signal-patterns.mjs';
|
|
16
15
|
import { resolveAnchorToken, formatAnchorError, resolveQueryAnchor, fetchRecentTimeline, fetchTimelineWindow } from './lib/timeline-core.mjs';
|
|
17
16
|
import { buildSearchFtsQuery, parseDateBounds, parseDuration, coreRunSearchPipeline } from './lib/search-core.mjs';
|
|
18
17
|
import {
|
|
19
18
|
cleanupBroken, decayAndMarkIdle, boostAccessed, demotePinned, mergeDuplicates,
|
|
20
19
|
recoverOrphanedChildren, recoverBuriedLessons, sweepDeferredWorkOrphans,
|
|
21
20
|
purgeStale, purgeStalePreview, findDuplicates, maintenanceStats, rebuildVectors, vacuum,
|
|
22
|
-
|
|
21
|
+
hardDeleteCandidateCount,
|
|
23
22
|
OP_CAP, STALE_AGE_MS,
|
|
24
23
|
} from './lib/maintain-core.mjs';
|
|
25
24
|
import { snapshotDb } from './lib/db-backup.mjs';
|
|
25
|
+
import { deleteObservations } from './lib/delete-core.mjs';
|
|
26
26
|
import { effectiveQuiet, RUNTIME_DIR } from './hook-shared.mjs';
|
|
27
27
|
import { TIER_CASE_SQL, tierSqlParams } from './tier.mjs';
|
|
28
|
+
import { computeStatsFeed } from './lib/stats-core.mjs';
|
|
29
|
+
import { buildLessonNudge } from './lib/save-nudge.mjs';
|
|
28
30
|
import { formatObsFieldValue } from './cli/common.mjs';
|
|
29
31
|
import { memSearchSchema, memRecentSchema, memTimelineSchema, memGetSchema, memDeleteSchema, memSaveSchema, memStatsSchema, memCompressSchema, memMaintainSchema, memOptimizeSchema, memUpdateSchema, memExportSchema, memRecallSchema, memFtsCheckSchema, memRegistrySchema, memBrowseSchema, memUseSchema, memDeferSchema, memDeferListSchema, memDeferDropSchema, tools as TOOL_DEFS } from './tool-schemas.mjs';
|
|
30
32
|
|
|
@@ -45,7 +47,6 @@ import { probeOtherSources as probeIdSources, bucketIdTokens } from './lib/id-ro
|
|
|
45
47
|
import { saveObservation } from './lib/save-observation.mjs';
|
|
46
48
|
import { rebuildObservationDerived } from './lib/observation-write.mjs';
|
|
47
49
|
import { EXPORT_COLUMNS_SQL } from './lib/export-columns.mjs';
|
|
48
|
-
import { computeNoiseGauge } from './lib/stats-quality.mjs';
|
|
49
50
|
import { recallByFile } from './lib/recall-core.mjs';
|
|
50
51
|
import { AUTO_MERGE_THRESHOLD } from './lib/dedup-constants.mjs';
|
|
51
52
|
import {
|
|
@@ -680,49 +681,14 @@ server.registerTool(
|
|
|
680
681
|
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
681
682
|
}
|
|
682
683
|
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
//
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
// Wrap cleanup + deletion in a transaction for consistency
|
|
689
|
-
const deletedIds = new Set(args.ids);
|
|
690
|
-
const deleteTx = db.transaction(() => {
|
|
691
|
-
// Clean up stale references in other observations' related_ids
|
|
692
|
-
// Use LIKE filter to avoid O(N) full-table scan — only fetch rows that may reference deleted IDs.
|
|
693
|
-
// NOTE: LIKE %id% has false positives (e.g. %1% matches [10], [21]). This is intentional —
|
|
694
|
-
// the LIKE is a coarse pre-filter; the JSON parse + Set.has below is the precise filter.
|
|
695
|
-
// Acceptable because observation count per user is typically <10K.
|
|
696
|
-
const likeConditions = args.ids.map(() => `related_ids LIKE ?`).join(' OR ');
|
|
697
|
-
const likeParams = args.ids.map(id => `%${id}%`);
|
|
698
|
-
const referencing = db.prepare(`
|
|
699
|
-
SELECT id, related_ids FROM observations
|
|
700
|
-
WHERE related_ids IS NOT NULL AND related_ids != '[]'
|
|
701
|
-
AND (${likeConditions})
|
|
702
|
-
`).all(...likeParams);
|
|
703
|
-
for (const r of referencing) {
|
|
704
|
-
let ids;
|
|
705
|
-
try { ids = JSON.parse(r.related_ids); } catch (e) { debugCatch(e, 'deleteRelatedIds'); continue; }
|
|
706
|
-
if (!Array.isArray(ids) || !ids.every(id => Number.isInteger(id))) continue;
|
|
707
|
-
const filtered = ids.filter(id => !deletedIds.has(id));
|
|
708
|
-
if (filtered.length !== ids.length) {
|
|
709
|
-
db.prepare('UPDATE observations SET related_ids = ? WHERE id = ?').run(JSON.stringify(filtered), r.id);
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
// Resurface rows merged/compressed INTO the doomed keepers before deleting, else
|
|
713
|
-
// they dangle behind a now-missing parent (compressed_into has no FK) — invisible
|
|
714
|
-
// to every COALESCE(compressed_into,0)=0 view and unrecoverable. Mirrors the CLI
|
|
715
|
-
// delete path + the maintain hard-delete guard (recoverChildrenOf).
|
|
716
|
-
const recovered = recoverChildrenOf(db, args.ids);
|
|
717
|
-
// Execute deletion (FTS5 cleanup handled by observations_ad trigger)
|
|
718
|
-
const deleted = db.prepare(`DELETE FROM observations WHERE id IN (${placeholders})`).run(...args.ids);
|
|
719
|
-
return { changes: deleted.changes, recovered };
|
|
720
|
-
});
|
|
721
|
-
const result = deleteTx();
|
|
684
|
+
// Full delete orchestration (snapshot + related_ids cleanup + child recovery +
|
|
685
|
+
// delete transaction) lives in lib/delete-core.mjs — single source of truth shared
|
|
686
|
+
// with the CLI `delete` path (was inlined + kept in sync by parity comments).
|
|
687
|
+
const result = deleteObservations(db, args.ids);
|
|
722
688
|
|
|
723
689
|
const missing = args.ids.filter(id => !rows.some(r => r.id === id));
|
|
724
|
-
const msg = [`Deleted ${result.
|
|
725
|
-
if (result.
|
|
690
|
+
const msg = [`Deleted ${result.deleted} observation(s).`];
|
|
691
|
+
if (result.recoveredChildren > 0) msg.push(`Recovered ${result.recoveredChildren} merged/compressed child observation(s) to live.`);
|
|
726
692
|
if (missing.length > 0) msg.push(`Note: ID(s) ${missing.join(', ')} not found.`);
|
|
727
693
|
return { content: [{ type: 'text', text: msg.join(' ') }] };
|
|
728
694
|
})
|
|
@@ -784,7 +750,8 @@ server.registerTool(
|
|
|
784
750
|
const supersededNote = result.supersededIds && result.supersededIds.length > 0
|
|
785
751
|
? ` Superseded: ${result.supersededIds.map(i => `#${i}`).join(', ')}.`
|
|
786
752
|
: '';
|
|
787
|
-
|
|
753
|
+
const nudge = buildLessonNudge({ type: result.type, id: result.id, lessonCaptured: result.lessonCaptured, surface: 'mcp' });
|
|
754
|
+
return { content: [{ type: 'text', text: `Saved as observation #${result.id} [${result.type}] in project "${project}".${lessonNote}${closedNote}${supersededNote}${nudge}` }] };
|
|
788
755
|
})
|
|
789
756
|
);
|
|
790
757
|
|
|
@@ -882,97 +849,11 @@ server.registerTool(
|
|
|
882
849
|
return { content: [{ type: 'text', text: formatQualityReport(data) }] };
|
|
883
850
|
}
|
|
884
851
|
|
|
885
|
-
const
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
const obsTotal = db.prepare(`SELECT COUNT(*) as c FROM observations WHERE 1=1 ${projectFilter}`).get(...baseParams);
|
|
891
|
-
const sessTotal = db.prepare(`SELECT COUNT(*) as c FROM session_summaries WHERE 1=1 ${projectFilter}`).get(...baseParams);
|
|
892
|
-
const promptTotal = args.project
|
|
893
|
-
? db.prepare(`SELECT COUNT(*) as c FROM user_prompts p JOIN sdk_sessions s ON p.content_session_id = s.content_session_id WHERE s.project = ?`).get(args.project)
|
|
894
|
-
: db.prepare(`SELECT COUNT(*) as c FROM user_prompts`).get();
|
|
895
|
-
|
|
896
|
-
// Recent counts
|
|
897
|
-
const obsRecent = db.prepare(`SELECT COUNT(*) as c FROM observations WHERE created_at_epoch >= ? ${projectFilter}`).get(cutoff, ...baseParams);
|
|
898
|
-
const sessRecent = db.prepare(`SELECT COUNT(*) as c FROM session_summaries WHERE created_at_epoch >= ? ${projectFilter}`).get(cutoff, ...baseParams);
|
|
899
|
-
|
|
900
|
-
// Type distribution (recent)
|
|
901
|
-
const types = db.prepare(`
|
|
902
|
-
SELECT type, COUNT(*) as c FROM observations
|
|
903
|
-
WHERE created_at_epoch >= ? ${projectFilter}
|
|
904
|
-
GROUP BY type ORDER BY c DESC
|
|
905
|
-
`).all(cutoff, ...baseParams);
|
|
906
|
-
|
|
907
|
-
// Projects (global view — skipped when filtering by single project)
|
|
908
|
-
const projects = args.project ? [] : db.prepare(`
|
|
909
|
-
SELECT project, COUNT(*) as c FROM observations
|
|
910
|
-
GROUP BY project ORDER BY c DESC
|
|
911
|
-
LIMIT 20
|
|
912
|
-
`).all();
|
|
913
|
-
|
|
914
|
-
// Daily activity (last 7 days)
|
|
915
|
-
const daily = db.prepare(`
|
|
916
|
-
SELECT date(created_at) as day, COUNT(*) as c FROM observations
|
|
917
|
-
WHERE created_at_epoch >= ? ${projectFilter}
|
|
918
|
-
GROUP BY day ORDER BY day DESC
|
|
919
|
-
LIMIT 7
|
|
920
|
-
`).all(Date.now() - 7 * 86400000, ...baseParams);
|
|
921
|
-
|
|
922
|
-
// Health metrics
|
|
923
|
-
const tokenEst = db.prepare(`
|
|
924
|
-
SELECT SUM(LENGTH(COALESCE(title,'')) + LENGTH(COALESCE(narrative,'')) + LENGTH(COALESCE(text,''))) / 4 as t
|
|
925
|
-
FROM observations WHERE 1=1 ${projectFilter}
|
|
926
|
-
`).get(...baseParams);
|
|
927
|
-
|
|
928
|
-
const avgImp = db.prepare(`
|
|
929
|
-
SELECT AVG(COALESCE(importance,1)) as v FROM observations WHERE 1=1 ${projectFilter}
|
|
930
|
-
`).get(...baseParams);
|
|
931
|
-
|
|
932
|
-
const thirtyDaysAgo = Date.now() - 30 * 86400000;
|
|
933
|
-
// v3.23 noise-gauge de-blinding (mirrors mem-cli cmdStats): `<= 1` makes the
|
|
934
|
-
// imp=0 dormant population visible (decay floor + LLM low-signal filter push
|
|
935
|
-
// ~half the live corpus to 0); injection_count=0 mirrors decay's NEVER-INJECTED
|
|
936
|
-
// guard so injected-but-decayed pinned noise isn't miscounted as "never used".
|
|
937
|
-
const lowVal = db.prepare(`
|
|
938
|
-
SELECT COUNT(*) as c FROM observations
|
|
939
|
-
WHERE COALESCE(importance,1) <= 1 AND COALESCE(access_count,0) = 0
|
|
940
|
-
AND COALESCE(injection_count,0) = 0
|
|
941
|
-
AND COALESCE(compressed_into, 0) = 0
|
|
942
|
-
AND created_at_epoch < ? ${projectFilter}
|
|
943
|
-
`).get(thirtyDaysAgo, ...baseParams);
|
|
944
|
-
|
|
945
|
-
// Low-signal-title population (template / tool-log titles the read-side filter
|
|
946
|
-
// already excludes). The imp=1 "Low-value" metric can't see these, so the
|
|
947
|
-
// gauge under-reports real noise without it. See lib/low-signal-patterns.mjs.
|
|
948
|
-
const lowSignalTitle = db.prepare(`
|
|
949
|
-
SELECT COUNT(*) as c FROM observations
|
|
950
|
-
WHERE NOT ${buildNotLowSignalSql()}
|
|
951
|
-
AND COALESCE(compressed_into, 0) = 0 ${projectFilter}
|
|
952
|
-
`).get(...baseParams);
|
|
953
|
-
// F7: both noise numerators exclude compressed rows → divide by the LIVE count, not
|
|
954
|
-
// obsTotal (all rows), so a compress-heavy store isn't reported cleaner than it is.
|
|
955
|
-
const liveTotal = db.prepare(
|
|
956
|
-
`SELECT COUNT(*) as c FROM observations WHERE COALESCE(compressed_into, 0) = 0 ${projectFilter}`
|
|
957
|
-
).get(...baseParams);
|
|
958
|
-
const { noiseRatio, lowSignalRatio } = computeNoiseGauge({ liveTotal: liveTotal.c, lowValCount: lowVal.c, lowSignalCount: lowSignalTitle.c });
|
|
959
|
-
const compressedCount = db.prepare(`
|
|
960
|
-
SELECT COUNT(*) as c FROM observations WHERE compressed_into IS NOT NULL ${projectFilter}
|
|
961
|
-
`).get(...baseParams);
|
|
962
|
-
const supersededOnlyCount = db.prepare(`
|
|
963
|
-
SELECT COUNT(*) as c FROM observations WHERE superseded_at IS NOT NULL AND compressed_into IS NULL ${projectFilter}
|
|
964
|
-
`).get(...baseParams);
|
|
965
|
-
|
|
966
|
-
// Tier distribution
|
|
967
|
-
const tierCtx = { now: Date.now(), currentProject: args.project || inferProject(), currentSessionId: '' };
|
|
968
|
-
const tdParams = tierSqlParams(tierCtx);
|
|
969
|
-
const tierDist = db.prepare(`
|
|
970
|
-
SELECT tier, COUNT(*) as c FROM (
|
|
971
|
-
SELECT ${TIER_CASE_SQL} as tier FROM observations
|
|
972
|
-
WHERE COALESCE(compressed_into, 0) = 0 AND superseded_at IS NULL ${projectFilter}
|
|
973
|
-
) GROUP BY tier ORDER BY tier
|
|
974
|
-
`).all(...tdParams, ...baseParams);
|
|
975
|
-
const tierMap = Object.fromEntries(tierDist.map(r => [r.tier, r.c]));
|
|
852
|
+
const {
|
|
853
|
+
obsTotal, sessTotal, promptTotal, obsRecent, sessRecent,
|
|
854
|
+
types, projects, daily, tokenEst, avgImp, lowVal, lowSignalTitle,
|
|
855
|
+
noiseRatio, lowSignalRatio, compressedCount, supersededOnlyCount, tierMap,
|
|
856
|
+
} = computeStatsFeed(db, { project: args.project || null, days });
|
|
976
857
|
|
|
977
858
|
const lines = [
|
|
978
859
|
`Memory Statistics${args.project ? ` (project: ${args.project})` : ''}:`,
|
package/source-files.mjs
CHANGED
|
@@ -152,6 +152,29 @@ export const SOURCE_FILES = [
|
|
|
152
152
|
// server.mjs, and hook.mjs before their destructive purge/cleanup — missing it
|
|
153
153
|
// would crash maintain on auto-update with an unresolved import.
|
|
154
154
|
'lib/db-backup.mjs',
|
|
155
|
+
// HIGH-1 events-injection: surfaces the `events` canonical store into the passive
|
|
156
|
+
// injection surfaces. Statically imported by hook.mjs (UserPromptSubmit) and
|
|
157
|
+
// hook-context.mjs (SessionStart) — missing it from the manifest would break both
|
|
158
|
+
// hooks on auto-update.
|
|
159
|
+
'lib/events-injection.mjs',
|
|
160
|
+
// Shared delete orchestration (snapshot + related_ids cleanup + child recovery
|
|
161
|
+
// + delete txn). Statically imported by server.mjs (mem_delete) and mem-cli.mjs
|
|
162
|
+
// (cmdDelete) — extracted to kill the byte-duplicated twin. Missing it from the
|
|
163
|
+
// manifest would leave both delete surfaces unsigned/broken on auto-update.
|
|
164
|
+
'lib/delete-core.mjs',
|
|
165
|
+
// Shared primary stats feed (audit 2026-07-17 MED-4) — statically imported by
|
|
166
|
+
// server.mjs (mem_stats) and mem-cli.mjs (cmdStats); killed the ~80-line
|
|
167
|
+
// byte-duplicated twin. Missing it breaks both stats surfaces on auto-update.
|
|
168
|
+
'lib/stats-core.mjs',
|
|
169
|
+
// Observation `type` vocabulary single source (audit 2026-07-17 MED-3) —
|
|
170
|
+
// statically imported by tool-schemas.mjs, mem-cli.mjs, hook-llm.mjs,
|
|
171
|
+
// hook-optimize.mjs, lib/activity.mjs. Missing it breaks every save/validate
|
|
172
|
+
// path on auto-update.
|
|
173
|
+
'lib/obs-types.mjs',
|
|
174
|
+
// Save-time lesson nudge (audit 2026-07-17 P4) — statically imported by server.mjs
|
|
175
|
+
// (mem_save) and mem-cli.mjs (cmdSave). Missing it breaks both save surfaces on
|
|
176
|
+
// auto-update.
|
|
177
|
+
'lib/save-nudge.mjs',
|
|
155
178
|
// P10 dedup/merge threshold constants — single source of truth for the Jaccard
|
|
156
179
|
// dedup/merge cutoffs. Statically imported by hook.mjs, hook-llm.mjs,
|
|
157
180
|
// hook-optimize.mjs, mem-cli.mjs, server.mjs, and the save/maintain cores;
|
package/tool-schemas.mjs
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { CLI_INVOKE } from './cli-path.mjs';
|
|
6
|
+
import { OBS_TYPES } from './lib/obs-types.mjs';
|
|
6
7
|
|
|
7
|
-
export const OBS_TYPE_ENUM = z.enum([
|
|
8
|
+
export const OBS_TYPE_ENUM = z.enum([...OBS_TYPES]);
|
|
8
9
|
|
|
9
10
|
// LLM-friendly coercion: accept string numbers and normalize to proper types
|
|
10
11
|
const coerceInt = z.preprocess(
|