claude-mem-lite 2.28.2 → 2.30.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/cli.mjs +1 -1
- package/commands/mem.md +2 -1
- package/commands/memory.md +2 -1
- package/commands/tools.md +2 -1
- package/commands/update.md +2 -1
- package/haiku-client.mjs +103 -0
- package/hook-context.mjs +213 -32
- package/hook-memory.mjs +40 -17
- package/hook.mjs +36 -134
- package/install.mjs +1 -1
- package/mem-cli.mjs +248 -34
- package/nlp.mjs +26 -0
- package/package.json +1 -5
- package/project-utils.mjs +14 -1
- package/schema.mjs +2 -1
- package/scoring-sql.mjs +46 -6
- package/scripts/pre-tool-recall.js +35 -12
- package/scripts/prompt-search-utils.mjs +39 -14
- package/scripts/user-prompt-search.js +10 -1
- package/server.mjs +123 -30
- package/skill.md +13 -26
- package/synonyms.mjs +79 -1
- package/tool-schemas.mjs +11 -0
- package/utils.mjs +9 -3
- package/commands/recall.md +0 -9
- package/commands/recent.md +0 -7
- package/commands/search.md +0 -9
- package/commands/timeline.md +0 -7
package/hook.mjs
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
// Background workers (slow): llm-episode, llm-summary
|
|
6
6
|
|
|
7
7
|
import { randomUUID } from 'crypto';
|
|
8
|
-
import { join
|
|
8
|
+
import { join } from 'path';
|
|
9
9
|
import { readFileSync, writeFileSync, unlinkSync, readdirSync, renameSync, statSync } from 'fs';
|
|
10
10
|
import { homedir } from 'os';
|
|
11
11
|
import {
|
|
12
|
-
truncate,
|
|
12
|
+
truncate, inferProject, detectBashSignificance,
|
|
13
13
|
extractErrorKeywords, extractFilePaths, isRelatedToEpisode,
|
|
14
|
-
makeEntryDesc, scrubSecrets, EDIT_TOOLS, debugCatch, debugLog,
|
|
14
|
+
makeEntryDesc, scrubSecrets, EDIT_TOOLS, debugCatch, debugLog,
|
|
15
15
|
COMPRESSED_AUTO, COMPRESSED_PENDING_PURGE, isoWeekKey, OBS_BM25,
|
|
16
16
|
} from './utils.mjs';
|
|
17
17
|
import {
|
|
@@ -20,10 +20,10 @@ import {
|
|
|
20
20
|
createEpisode, addFileToEpisode,
|
|
21
21
|
writePendingEntry, mergePendingEntries, episodeHasSignificantContent,
|
|
22
22
|
} from './hook-episode.mjs';
|
|
23
|
-
import {
|
|
23
|
+
import { cleanupClaudeMdLegacyBlock, buildSessionContextLines } from './hook-context.mjs';
|
|
24
24
|
import {
|
|
25
25
|
RUNTIME_DIR, EPISODE_BUFFER_SIZE, EPISODE_TIME_GAP_MS,
|
|
26
|
-
SESSION_EXPIRY_MS, STALE_SESSION_MS, STALE_LOCK_MS,
|
|
26
|
+
SESSION_EXPIRY_MS, STALE_SESSION_MS, STALE_LOCK_MS,
|
|
27
27
|
sessionFile, getSessionId, createSessionId, openDb,
|
|
28
28
|
spawnBackground,
|
|
29
29
|
} from './hook-shared.mjs';
|
|
@@ -31,13 +31,14 @@ import { handleLLMEpisode, handleLLMSummary, saveObservation, buildImmediateObse
|
|
|
31
31
|
import { searchRelevantMemories } from './hook-memory.mjs';
|
|
32
32
|
import { buildAndSaveHandoff, detectContinuationIntent, renderHandoffInjection, extractUnfinishedSummary } from './hook-handoff.mjs';
|
|
33
33
|
import { checkForUpdate } from './hook-update.mjs';
|
|
34
|
+
import { handleLLMOptimize } from './hook-optimize.mjs';
|
|
34
35
|
import { SKIP_TOOLS, SKIP_PREFIXES } from './skip-tools.mjs';
|
|
35
36
|
import { getVocabulary } from './tfidf.mjs';
|
|
36
37
|
|
|
37
38
|
// Prevent recursive hooks from background claude -p calls
|
|
38
39
|
// Background workers (llm-episode, llm-summary) are exempt — they're ours
|
|
39
40
|
const event = process.argv[2];
|
|
40
|
-
const BG_EVENTS = new Set(['llm-episode', 'llm-summary', 'auto-compress']);
|
|
41
|
+
const BG_EVENTS = new Set(['llm-episode', 'llm-summary', 'auto-compress', 'llm-optimize']);
|
|
41
42
|
|
|
42
43
|
// Respect Claude Code plugin disable state even when legacy settings.json hooks remain.
|
|
43
44
|
// install.mjs writes direct hooks into ~/.claude/settings.json, so disabling the plugin
|
|
@@ -122,6 +123,22 @@ function flushEpisode(episode) {
|
|
|
122
123
|
|
|
123
124
|
if (isSignificant) {
|
|
124
125
|
spawnBackground('llm-episode', flushFile);
|
|
126
|
+
|
|
127
|
+
// P3: Auto-save hint — detect error→fix pattern (error entry followed by Edit/Write)
|
|
128
|
+
// and nudge Claude to save the lesson for future recall
|
|
129
|
+
try {
|
|
130
|
+
const entries = episode.entries || [];
|
|
131
|
+
const hasError = entries.some(e => e.isError);
|
|
132
|
+
const hasEdit = entries.some(e => EDIT_TOOLS.has(e.tool));
|
|
133
|
+
if (hasError && hasEdit && entries.length >= 3) {
|
|
134
|
+
const editFiles = entries.filter(e => EDIT_TOOLS.has(e.tool)).flatMap(e => e.files || []);
|
|
135
|
+
const uniqueFiles = [...new Set(editFiles)].slice(0, 3);
|
|
136
|
+
const filesHint = uniqueFiles.length > 0 ? ` (files: ${uniqueFiles.join(', ')})` : '';
|
|
137
|
+
process.stdout.write(
|
|
138
|
+
`[mem] 💡 Error→fix pattern detected${filesHint}. Consider: mem_save(type="bugfix", lesson_learned="root cause & fix")\n`,
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} catch { /* never block on hint */ }
|
|
125
142
|
} else {
|
|
126
143
|
try { unlinkSync(flushFile); } catch {}
|
|
127
144
|
}
|
|
@@ -538,7 +555,8 @@ async function handleSessionStart() {
|
|
|
538
555
|
// Mark maintenance as done (24h gate) — even though compression runs in background
|
|
539
556
|
writeFileSync(maintainFile, JSON.stringify({ epoch: Date.now() }));
|
|
540
557
|
// Weekly summary grouping runs in background to avoid blocking SessionStart
|
|
541
|
-
spawnBackground('auto-compress');
|
|
558
|
+
if (!process.env.CLAUDE_MEM_SKIP_COMPRESS) spawnBackground('auto-compress');
|
|
559
|
+
if (!process.env.CLAUDE_MEM_SKIP_OPTIMIZE) spawnBackground('llm-optimize');
|
|
542
560
|
} catch (e) { debugCatch(e, 'auto-maintain'); }
|
|
543
561
|
}
|
|
544
562
|
|
|
@@ -627,28 +645,6 @@ async function handleSessionStart() {
|
|
|
627
645
|
}
|
|
628
646
|
} catch {}
|
|
629
647
|
|
|
630
|
-
// Token-budgeted observation selection (replaces flat LIMIT 15)
|
|
631
|
-
const selected = selectWithTokenBudget(db, project, 2000);
|
|
632
|
-
const observations = selected.observations;
|
|
633
|
-
|
|
634
|
-
// Fallback: recent across all projects with tiered windows (M7: local variable for clarity)
|
|
635
|
-
let fallbackObs = [];
|
|
636
|
-
if (observations.length < 3) {
|
|
637
|
-
const fbOneDayAgo = Date.now() - STALE_SESSION_MS;
|
|
638
|
-
const fbSevenDaysAgo = Date.now() - FALLBACK_OBS_WINDOW_MS;
|
|
639
|
-
fallbackObs = db.prepare(`
|
|
640
|
-
SELECT id, type, title, project, created_at
|
|
641
|
-
FROM observations
|
|
642
|
-
WHERE COALESCE(compressed_into, 0) = 0
|
|
643
|
-
AND (
|
|
644
|
-
(created_at_epoch > ? AND importance >= 1)
|
|
645
|
-
OR (created_at_epoch > ? AND importance >= 2)
|
|
646
|
-
)
|
|
647
|
-
ORDER BY created_at_epoch DESC
|
|
648
|
-
LIMIT 5
|
|
649
|
-
`).all(fbOneDayAgo, fbSevenDaysAgo);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
648
|
// Fallback fast summary: if a recently completed session has no summary yet
|
|
653
649
|
// (e.g. /exit → fast restart before Haiku finishes), build one synchronously.
|
|
654
650
|
// Skipped when prevSessionId is set (already handled above).
|
|
@@ -690,114 +686,19 @@ async function handleSessionStart() {
|
|
|
690
686
|
} catch (e) { debugCatch(e, 'session-start-exit-fast-summary'); }
|
|
691
687
|
}
|
|
692
688
|
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
WHERE project = ?
|
|
698
|
-
ORDER BY created_at_epoch DESC
|
|
699
|
-
LIMIT 1
|
|
700
|
-
`).get(project);
|
|
701
|
-
|
|
702
|
-
// Build summary lines (shared by stdout and CLAUDE.md)
|
|
703
|
-
const summaryLines = buildSummaryLines(latestSummary);
|
|
704
|
-
|
|
705
|
-
// Key context: top high-importance observations for CLAUDE.md persistence
|
|
706
|
-
// Split into "File Lessons" (actionable, has lesson + file) and "Key Context" (informational)
|
|
707
|
-
const keyObs = db.prepare(`
|
|
708
|
-
SELECT o.id, o.type, o.title, o.lesson_learned, o.files_modified FROM observations o
|
|
709
|
-
WHERE o.project = ? AND COALESCE(o.compressed_into, 0) = 0
|
|
710
|
-
AND o.superseded_at IS NULL
|
|
711
|
-
AND COALESCE(o.importance, 1) >= 2
|
|
712
|
-
ORDER BY o.created_at_epoch DESC LIMIT 10
|
|
713
|
-
`).all(project);
|
|
714
|
-
|
|
715
|
-
if (keyObs.length > 0) {
|
|
716
|
-
const fileLessons = [];
|
|
717
|
-
const keyContext = [];
|
|
718
|
-
|
|
719
|
-
for (const o of keyObs) {
|
|
720
|
-
const clean = (o.title || '(untitled)')
|
|
721
|
-
.replace(/ → (?:ERROR: )?\{".*$/, '')
|
|
722
|
-
.replace(/ → (?:ERROR: )?\{[^}]*\.{3}$/, '');
|
|
723
|
-
const hasLesson = o.lesson_learned && o.lesson_learned.trim();
|
|
724
|
-
const hasFiles = o.files_modified && o.files_modified !== '[]';
|
|
725
|
-
|
|
726
|
-
if (hasLesson && hasFiles) {
|
|
727
|
-
try {
|
|
728
|
-
const files = JSON.parse(o.files_modified);
|
|
729
|
-
const fname = basename(Array.isArray(files) && files.length > 0 ? files[0] : '');
|
|
730
|
-
if (fname) {
|
|
731
|
-
fileLessons.push(`- ${fname}: ${truncate(o.lesson_learned, 100)} (#${o.id})`);
|
|
732
|
-
continue;
|
|
733
|
-
}
|
|
734
|
-
} catch {}
|
|
735
|
-
}
|
|
736
|
-
const lesson = hasLesson ? ` — ${truncate(o.lesson_learned, 60)}` : '';
|
|
737
|
-
keyContext.push(`- [${o.type || 'discovery'}] ${truncate(clean, 80)} (#${o.id})${lesson}`);
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (fileLessons.length > 0) {
|
|
741
|
-
summaryLines.push('### File Lessons');
|
|
742
|
-
summaryLines.push(...fileLessons.slice(0, 5));
|
|
743
|
-
summaryLines.push('');
|
|
744
|
-
}
|
|
745
|
-
if (keyContext.length > 0) {
|
|
746
|
-
summaryLines.push('### Key Context');
|
|
747
|
-
summaryLines.push(...keyContext.slice(0, 5));
|
|
748
|
-
summaryLines.push('');
|
|
749
|
-
}
|
|
750
|
-
} else if (!latestSummary) {
|
|
751
|
-
// Fallback: no summary AND no key observations — show recent activity
|
|
752
|
-
const recentObs = (observations.length >= 3 ? observations : fallbackObs).slice(0, 3);
|
|
753
|
-
if (recentObs.length > 0) {
|
|
754
|
-
summaryLines.push('### Recent Activity');
|
|
755
|
-
for (const o of recentObs) {
|
|
756
|
-
summaryLines.push(`- ${truncate(o.title || '(untitled)', 80)}`);
|
|
757
|
-
}
|
|
758
|
-
summaryLines.push('');
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// Working state from /clear handoff (persisted to both stdout and CLAUDE.md)
|
|
763
|
-
const handoffLines = [];
|
|
764
|
-
if (prevClearHandoff) {
|
|
765
|
-
handoffLines.push('### Working State (from /clear)');
|
|
766
|
-
if (prevClearHandoff.working_on) handoffLines.push(`- Working on: ${truncate(prevClearHandoff.working_on, 200)}`);
|
|
767
|
-
if (prevClearHandoff.unfinished) {
|
|
768
|
-
const pendingSummary = extractUnfinishedSummary(prevClearHandoff.unfinished);
|
|
769
|
-
if (pendingSummary) handoffLines.push(`- Unfinished: ${truncate(pendingSummary, 200)}`);
|
|
770
|
-
}
|
|
771
|
-
if (prevClearHandoff.key_files) {
|
|
772
|
-
try {
|
|
773
|
-
const files = JSON.parse(prevClearHandoff.key_files);
|
|
774
|
-
if (files.length > 0) handoffLines.push(`- Key files: ${files.map(f => basename(f)).join(', ')}`);
|
|
775
|
-
} catch {}
|
|
776
|
-
}
|
|
777
|
-
handoffLines.push('');
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Build observations table (stdout only — not persisted to CLAUDE.md)
|
|
781
|
-
const obsLines = [];
|
|
782
|
-
const obsToShow = observations.length >= 3 ? observations : fallbackObs;
|
|
783
|
-
if (obsToShow.length > 0) {
|
|
784
|
-
const today = now.toISOString().slice(0, 10);
|
|
785
|
-
obsLines.push(`### Recent (${today})`);
|
|
786
|
-
obsLines.push('');
|
|
787
|
-
obsLines.push('| ID | Time | T | Title |');
|
|
788
|
-
obsLines.push('|----|------|---|-------|');
|
|
789
|
-
for (const o of obsToShow) {
|
|
790
|
-
const proj = o.project ? ` (${o.project})` : '';
|
|
791
|
-
obsLines.push(`| #${o.id} | ${fmtTime(o.created_at)} | ${typeIcon(o.type)} | ${truncate(o.title || '(untitled)', 60)}${proj} |`);
|
|
792
|
-
}
|
|
793
|
-
}
|
|
689
|
+
// Build the full context body via shared helper (also used by `mem-cli context`).
|
|
690
|
+
// Queries session_summaries, key observations, clear handoff, and the
|
|
691
|
+
// token-budgeted observation pool directly from the DB.
|
|
692
|
+
const fullContext = buildSessionContextLines(db, project, now);
|
|
794
693
|
|
|
795
|
-
// Stdout
|
|
796
|
-
|
|
694
|
+
// Stdout is the sole context-delivery channel. The SessionStart hook output
|
|
695
|
+
// is injected as a <system-reminder> at session start, giving Claude the
|
|
696
|
+
// full summary + handoff state + observations table fresh from the DB.
|
|
797
697
|
process.stdout.write(`<claude-mem-context>\n${fullContext}\n</claude-mem-context>\n`);
|
|
798
698
|
|
|
799
|
-
//
|
|
800
|
-
|
|
699
|
+
// One-time migration: remove any stale <claude-mem-context> block left in
|
|
700
|
+
// CLAUDE.md by pre-v2.30 installs. Idempotent no-op afterwards.
|
|
701
|
+
cleanupClaudeMdLegacyBlock();
|
|
801
702
|
|
|
802
703
|
// Pre-load TF-IDF vocabulary cache for this session (from DB, ~1ms)
|
|
803
704
|
try { getVocabulary(db); } catch (e) { debugCatch(e, 'session-start-vocab'); }
|
|
@@ -1060,6 +961,7 @@ try {
|
|
|
1060
961
|
case 'llm-episode': await handleLLMEpisode(); break;
|
|
1061
962
|
case 'llm-summary': await handleLLMSummary(); break;
|
|
1062
963
|
case 'auto-compress': handleAutoCompress(); break;
|
|
964
|
+
case 'llm-optimize': await handleLLMOptimize(); break;
|
|
1063
965
|
}
|
|
1064
966
|
} catch (err) {
|
|
1065
967
|
// Always log fatal errors (ungated) with structured format
|
package/install.mjs
CHANGED
|
@@ -204,7 +204,7 @@ async function install() {
|
|
|
204
204
|
const SOURCE_FILES = [
|
|
205
205
|
'cli.mjs', 'server.mjs', 'server-internals.mjs', 'tool-schemas.mjs',
|
|
206
206
|
'hook.mjs', 'hook-shared.mjs', 'hook-llm.mjs', 'hook-memory.mjs', 'skip-tools.mjs',
|
|
207
|
-
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs',
|
|
207
|
+
'hook-semaphore.mjs', 'hook-episode.mjs', 'hook-context.mjs', 'hook-handoff.mjs', 'hook-update.mjs', 'hook-optimize.mjs',
|
|
208
208
|
'haiku-client.mjs', 'utils.mjs', 'schema.mjs', 'package.json', 'package-lock.json', 'skill.md',
|
|
209
209
|
'registry.mjs', 'registry-scanner.mjs', 'registry-indexer.mjs',
|
|
210
210
|
'registry-retriever.mjs', 'resource-discovery.mjs',
|