claude-recall 0.20.5 → 0.20.6

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.
@@ -8,7 +8,7 @@ source: claude-recall
8
8
 
9
9
  # Corrections
10
10
 
11
- Auto-generated from 12 memories. Last updated: 2026-04-02.
11
+ Auto-generated from 14 memories. Last updated: 2026-04-06.
12
12
 
13
13
  ## Rules
14
14
 
@@ -22,6 +22,8 @@ Auto-generated from 12 memories. Last updated: 2026-04-02.
22
22
  - CORRECTION: Memory with complex metadata
23
23
  - CORRECTION: Memory with complex metadata
24
24
  - CORRECTION: Memory with complex metadata
25
+ - CORRECTION: Memory with complex metadata
26
+ - CORRECTION: Memory with complex metadata
25
27
  - CORRECTION: License copyright should include user's name instead of 'Claude Recall Contributors'
26
28
  - CORRECTION: License copyright should list your name instead of 'Claude Recall Contributors'
27
29
 
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "topicId": "corrections",
3
- "sourceHash": "10b0ac995ebe0c54ad0a5972f28ba4a63e3429879bfb5aa92add0e616711aa33",
4
- "memoryCount": 12,
5
- "generatedAt": "2026-04-02T22:43:06.559Z",
3
+ "sourceHash": "c77b49a2f99b869465aeb4705a4c3de1f922381d5b49ed8d78c661477f8668fa",
4
+ "memoryCount": 14,
5
+ "generatedAt": "2026-04-06T16:14:29.343Z",
6
6
  "memoryKeys": [
7
+ "memory_1775492069326_vksvzmt3f",
8
+ "memory_1775491767369_sepsjmg8y",
7
9
  "memory_1775169786543_43p8to1hu",
8
10
  "memory_1775169704632_wzwczltzu",
9
11
  "memory_1775169639101_rmxkftqtk",
@@ -8,10 +8,25 @@ source: claude-recall
8
8
 
9
9
  # Preferences
10
10
 
11
- Auto-generated from 54 memories. Last updated: 2026-04-02.
11
+ Auto-generated from 69 memories. Last updated: 2026-04-06.
12
12
 
13
13
  ## Rules
14
14
 
15
+ - Session test preference 1775492069465
16
+ - Test preference 1775492069353-2
17
+ - Test preference 1775492069353-1
18
+ - Test preference 1775492069353-0
19
+ - Test memory content
20
+ - Session test preference 1775491767519
21
+ - Test preference 1775491767395-2
22
+ - Test preference 1775491767395-1
23
+ - Test preference 1775491767395-0
24
+ - Test memory content
25
+ - When planning implementation work, always divide into phases/stages. Each phase must have its own verification tests with concrete commands and expected outputs. Only proceed to the next phase when the current phase's tests pass. Never combine untested changes into a single deployment. This prevents hours of debugging cascading failures.
26
+ - Solving issues and testing that they are solved takes priority over committing and pushing. Don't suggest committing until the fix is verified end-to-end.
27
+ - When the user says "jam" it means "just answer me" — give a direct, concise answer without extra exploration, tool calls, or elaboration. Skip the research and just respond.
28
+ - After each refactoring, document the changes made. Don't batch documentation to the end — write it as you go.
29
+ - Any major refactoring requires exhaustive search to make sure nothing is missed. Always grep/search comprehensively before and after changes to verify no stale references, broken imports, or missed files remain.
15
30
  - Session test preference 1775169786712
16
31
  - Test preference 1775169786565-2
17
32
  - Test preference 1775169786565-1
@@ -1,9 +1,24 @@
1
1
  {
2
2
  "topicId": "preferences",
3
- "sourceHash": "d953115f40d7e590f2e4b1b034582b78ba1f1faa0d1521a6e107538ad328327d",
4
- "memoryCount": 54,
5
- "generatedAt": "2026-04-02T22:43:06.764Z",
3
+ "sourceHash": "84b3e678b896e5e6761dcfc6d49ee47c0027d4c7808f6ef6f8f3e00aa39ee76c",
4
+ "memoryCount": 69,
5
+ "generatedAt": "2026-04-06T16:14:29.485Z",
6
6
  "memoryKeys": [
7
+ "memory_1775492069467_5cturlg0a",
8
+ "memory_1775492069400_icg4tjivf",
9
+ "memory_1775492069377_goix7nu9v",
10
+ "memory_1775492069354_wma3zh5i7",
11
+ "memory_1775492069258_q9d2k28wt",
12
+ "memory_1775491767523_p2xtn4uak",
13
+ "memory_1775491767447_q1dwsdfk3",
14
+ "memory_1775491767421_vgntf4jt8",
15
+ "memory_1775491767397_f181w5lqd",
16
+ "memory_1775491767290_s7ntmkwpg",
17
+ "memory_1775491073130_p8b493ay9",
18
+ "memory_1775236195716_i3pb5nls7",
19
+ "memory_1775210227089_j433ldlva",
20
+ "memory_1775208934902_2kovciriy",
21
+ "memory_1775208477621_fqa3w21j1",
7
22
  "memory_1775169786717_1zmwoe6ai",
8
23
  "memory_1775169786630_rdudb8hbc",
9
24
  "memory_1775169786589_zurej1v51",
@@ -34,10 +34,11 @@ Persistent memory system that ensures Claude never repeats mistakes and always a
34
34
 
35
35
  1. **ALWAYS load rules before acting** — Call `load_rules` as your very first action in a session, before even reading files. Rules inform how you explore, not just how you edit.
36
36
  2. **ACT on loaded rules** — After loading, state which rules apply to your current task before proceeding. If a rule conflicts with your plan, follow the rule. If none apply, say so. Loading without applying is the same as not loading.
37
- 3. **Cite applied rules inline** — When a rule influences your work: (applied from memory: <rule>)
38
- 4. **Ask before storing** Before calling `store_memory`, tell the user what you plan to store and ask for confirmation
39
- 5. **Capture corrections immediately** — User fixes are highest priority (still ask first)
40
- 6. **Never store secrets** — No API keys, passwords, tokens, or PII
37
+ 3. **Cite applied rules inline** — When a rule influences your work: (applied from memory: <rule>). Place the citation next to the action it influenced, not at the end of unrelated text.
38
+ 4. **User says "recall" / "remember" / "store this" → use Claude Recall** — When the user says any of these keywords, ALWAYS use `mcp__claude-recall__store_memory`. Do NOT write to the native memory directory (`~/.claude/projects/*/memory/`) for these requests. Claude Recall is the user's preferred memory system.
39
+ 5. **Ask before storing** — Before calling `store_memory`, tell the user what you plan to store and ask for confirmation
40
+ 6. **Capture corrections immediately** — User fixes are highest priority (still ask first)
41
+ 7. **Never store secrets** — No API keys, passwords, tokens, or PII
41
42
 
42
43
  ## Quick Reference
43
44
 
@@ -8,6 +8,7 @@
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.classifyWithLLM = classifyWithLLM;
10
10
  exports.extractHindsightHint = extractHindsightHint;
11
+ exports.extractSessionLearningsWithLLM = extractSessionLearningsWithLLM;
11
12
  exports.classifyBatchWithLLM = classifyBatchWithLLM;
12
13
  // Lazy singleton — avoid import cost when API key is absent
13
14
  let clientInstance; // undefined = not yet checked
@@ -148,6 +149,64 @@ async function extractHindsightHint(failureDescription, context) {
148
149
  return null;
149
150
  }
150
151
  }
152
+ const SESSION_EXTRACTION_PROMPT = `You are analyzing a coding session transcript to extract durable project knowledge.
153
+
154
+ The transcript shows tool calls (Bash, Edit, Read, Grep, etc.) and their results, plus user and assistant messages.
155
+
156
+ Extract ONLY facts useful in FUTURE sessions:
157
+ - Project conventions discovered (file structure, naming patterns, build tools, test frameworks)
158
+ - Workflow patterns that worked or failed (e.g. "tests must be run from project root")
159
+ - Technical constraints or gotchas encountered (e.g. "this project uses ESM, not CJS")
160
+ - Environment requirements discovered (e.g. "needs Node 20+", "uses pnpm not npm")
161
+
162
+ Do NOT extract:
163
+ - Task-specific details (what was built, which files changed this session)
164
+ - Debugging steps unlikely to recur
165
+ - Code patterns visible by reading the codebase
166
+ - Anything in the EXISTING MEMORIES list below
167
+
168
+ Respond with ONLY valid JSON (no markdown fences):
169
+ [{"type":"project-knowledge|preference|devops|failure","content":"<imperative statement>","confidence":0.0-1.0}]
170
+
171
+ Return [] if nothing durable was learned. Max 10 items. Each content should be a concise imperative statement (e.g. "Run tests with pnpm test, not npm test").`;
172
+ /**
173
+ * Extract durable session learnings from a conversation summary using Haiku.
174
+ * Returns null if no API key or on any failure.
175
+ */
176
+ async function extractSessionLearningsWithLLM(summary, existingMemories) {
177
+ const client = getClient();
178
+ if (!client)
179
+ return null;
180
+ try {
181
+ const memList = existingMemories.length > 0
182
+ ? existingMemories.map(m => `- ${m}`).join('\n')
183
+ : '(none)';
184
+ const systemPrompt = SESSION_EXTRACTION_PROMPT + `\n\nEXISTING MEMORIES (do not duplicate):\n${memList}`;
185
+ const response = await client.messages.create({
186
+ model: MODEL,
187
+ max_tokens: 1000,
188
+ system: systemPrompt,
189
+ messages: [{ role: 'user', content: summary }],
190
+ });
191
+ const content = response.content?.[0];
192
+ if (content?.type !== 'text')
193
+ return null;
194
+ const results = parseJSON(content.text);
195
+ if (!Array.isArray(results))
196
+ return null;
197
+ const validTypes = ['project-knowledge', 'preference', 'devops', 'failure'];
198
+ return results
199
+ .filter((r) => r && validTypes.includes(r.type) && typeof r.content === 'string' && r.content.length > 5)
200
+ .map((r) => ({
201
+ type: r.type,
202
+ content: r.content,
203
+ confidence: typeof r.confidence === 'number' ? r.confidence : 0.7,
204
+ }));
205
+ }
206
+ catch {
207
+ return null;
208
+ }
209
+ }
151
210
  async function classifyBatchWithLLM(texts) {
152
211
  if (texts.length === 0)
153
212
  return [];
@@ -46,6 +46,7 @@ const memory_1 = require("../services/memory");
46
46
  const config_1 = require("../services/config");
47
47
  const failure_detectors_1 = require("./failure-detectors");
48
48
  const outcome_storage_1 = require("../services/outcome-storage");
49
+ const event_processors_1 = require("../shared/event-processors");
49
50
  const MAX_STORE = 3;
50
51
  async function handleMemoryStop(input) {
51
52
  const transcriptPath = input?.transcript_path ?? '';
@@ -107,6 +108,22 @@ async function handleMemoryStop(input) {
107
108
  (0, shared_1.hookLog)('memory-stop', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
108
109
  }
109
110
  (0, shared_1.hookLog)('memory-stop', `Session end: stored ${stored} memories from ${entries.length} entries`);
111
+ // Session extraction: learn from long coding sessions (reads wider window)
112
+ try {
113
+ (0, event_processors_1.setLogFunction)(shared_1.hookLog);
114
+ const sessionEntries = (0, shared_1.readTranscriptTail)(transcriptPath, 50);
115
+ if (sessionEntries.length >= 10) {
116
+ const conversationEntries = buildConversationEntries(sessionEntries);
117
+ const extracted = await (0, event_processors_1.extractSessionLearnings)(conversationEntries, input?.session_id ?? '', projectId, 5);
118
+ if (extracted > 0) {
119
+ (0, shared_1.hookLog)('memory-stop', `Session extraction: stored ${extracted} learnings`);
120
+ stored += extracted;
121
+ }
122
+ }
123
+ }
124
+ catch (err) {
125
+ (0, shared_1.hookLog)('memory-stop', `Session extraction error: ${(0, shared_1.safeErrorMessage)(err)}`);
126
+ }
110
127
  // Scan for citations in assistant messages to track compliance
111
128
  scanForCitations(transcriptPath);
112
129
  // Scan transcript for failure signals (non-zero exits, test cycles, backtracking, etc.)
@@ -385,3 +402,48 @@ function extractTagsFromContext(context) {
385
402
  }
386
403
  return tags;
387
404
  }
405
+ /**
406
+ * Convert raw JSONL transcript entries to ConversationEntry[] for session extraction.
407
+ */
408
+ function buildConversationEntries(entries) {
409
+ const result = [];
410
+ for (const entry of entries) {
411
+ if ((0, shared_1.isUserEntry)(entry)) {
412
+ const text = (0, shared_1.extractTextFromEntry)(entry);
413
+ if (text && text.length > 5) {
414
+ result.push({ role: 'user', text: text.substring(0, 300) });
415
+ }
416
+ }
417
+ else {
418
+ const text = (0, shared_1.extractTextFromEntry)(entry);
419
+ if (text && text.length > 5) {
420
+ result.push({ role: 'assistant', text: text.substring(0, 300) });
421
+ }
422
+ }
423
+ }
424
+ // Extract paired tool interactions across all entries
425
+ try {
426
+ const interactions = (0, shared_1.extractToolInteractions)(entries);
427
+ for (const ti of interactions) {
428
+ if (ti.call) {
429
+ result.push({
430
+ role: 'assistant',
431
+ text: JSON.stringify(ti.call.input || {}).substring(0, 150),
432
+ toolName: ti.call.name,
433
+ });
434
+ }
435
+ if (ti.result) {
436
+ result.push({
437
+ role: 'tool_result',
438
+ text: ti.result.content.substring(0, 200),
439
+ toolName: ti.call.name,
440
+ isError: ti.result.isError,
441
+ });
442
+ }
443
+ }
444
+ }
445
+ catch {
446
+ // Skip if parsing fails
447
+ }
448
+ return result;
449
+ }
@@ -64,6 +64,7 @@ function formatRules(rules) {
64
64
  function default_1(pi) {
65
65
  let projectId = '';
66
66
  let sessionId = `pi_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`;
67
+ const collectedToolResults = [];
67
68
  let rulesLoaded = false;
68
69
  const collectedUserTexts = [];
69
70
  // Route logs through Pi's UI when available
@@ -78,6 +79,7 @@ function default_1(pi) {
78
79
  projectId = ctx.cwd.split('/').pop() || 'unknown';
79
80
  rulesLoaded = false;
80
81
  collectedUserTexts.length = 0;
82
+ collectedToolResults.length = 0;
81
83
  (0, event_processors_1.resetPendingFailures)();
82
84
  try {
83
85
  config_1.ConfigService.getInstance().updateConfig({
@@ -112,6 +114,13 @@ function default_1(pi) {
112
114
  .map(c => c.text)
113
115
  .join('\n');
114
116
  const result = (0, event_processors_1.processToolOutcome)(event.toolName, event.input, output, event.isError, sessionId);
117
+ // Collect for session extraction
118
+ collectedToolResults.push({
119
+ role: 'tool_result',
120
+ text: output.substring(0, 300),
121
+ toolName: event.toolName,
122
+ isError: event.isError,
123
+ });
115
124
  if (ctx.hasUI) {
116
125
  const label = event.input?.command
117
126
  ? truncateStr(event.input.command, 40)
@@ -133,9 +142,17 @@ function default_1(pi) {
133
142
  (0, event_processors_1.processUserInput)(event.text, sessionId).catch(() => { });
134
143
  return { action: 'continue' };
135
144
  });
136
- // --- Event: session end — episode + promotion ---
145
+ // --- Event: session end — episode + promotion + session extraction ---
137
146
  pi.on('session_shutdown', (_event, _ctx) => {
138
147
  (0, event_processors_1.processSessionEnd)(collectedUserTexts, sessionId, projectId).catch(() => { });
148
+ // Session extraction: learn from long coding sessions
149
+ const allEntries = [
150
+ ...collectedUserTexts.map(t => ({ role: 'user', text: t })),
151
+ ...collectedToolResults,
152
+ ];
153
+ if (allEntries.length >= 10) {
154
+ (0, event_processors_1.extractSessionLearnings)(allEntries, sessionId, projectId, 5).catch(() => { });
155
+ }
139
156
  });
140
157
  // --- Event: pre-compaction — aggressive capture ---
141
158
  pi.on('session_before_compact', (event, _ctx) => {
@@ -46,7 +46,10 @@ exports.processToolOutcome = processToolOutcome;
46
46
  exports.processUserInput = processUserInput;
47
47
  exports.processSessionEnd = processSessionEnd;
48
48
  exports.processPreCompact = processPreCompact;
49
+ exports.buildSummary = buildSummary;
50
+ exports.extractSessionLearnings = extractSessionLearnings;
49
51
  const shared_1 = require("../hooks/shared");
52
+ const llm_classifier_1 = require("../hooks/llm-classifier");
50
53
  const memory_1 = require("../services/memory");
51
54
  const outcome_storage_1 = require("../services/outcome-storage");
52
55
  let logFn = () => { }; // silent by default
@@ -401,3 +404,89 @@ async function processPreCompact(userTexts, sessionId, maxStore = 5) {
401
404
  }
402
405
  return stored;
403
406
  }
407
+ const SUMMARY_MAX_CHARS = 4000;
408
+ /**
409
+ * Build a condensed conversation summary from entries for LLM extraction.
410
+ */
411
+ function buildSummary(entries) {
412
+ const lines = [];
413
+ let totalChars = 0;
414
+ for (const entry of entries) {
415
+ let line;
416
+ if (entry.role === 'tool_result') {
417
+ const status = entry.isError ? ' [ERROR]' : '';
418
+ const tool = entry.toolName ? `${entry.toolName}` : 'tool';
419
+ line = `[${tool}${status}] ${truncate(entry.text, 200)}`;
420
+ }
421
+ else if (entry.role === 'assistant' && entry.toolName) {
422
+ line = `[assistant → ${entry.toolName}] ${truncate(entry.text, 150)}`;
423
+ }
424
+ else {
425
+ line = `[${entry.role}] ${truncate(entry.text, 200)}`;
426
+ }
427
+ if (totalChars + line.length > SUMMARY_MAX_CHARS)
428
+ break;
429
+ lines.push(line);
430
+ totalChars += line.length + 1;
431
+ }
432
+ return lines.join('\n');
433
+ }
434
+ /**
435
+ * Extract durable learnings from a coding session using LLM analysis.
436
+ *
437
+ * Sends a condensed session summary to Haiku and stores extracted
438
+ * project knowledge, preferences, and workflow patterns.
439
+ *
440
+ * Requires ANTHROPIC_API_KEY — logs a message if unavailable.
441
+ *
442
+ * @param entries Conversation entries (user + assistant + tool results)
443
+ * @param sessionId Current session ID
444
+ * @param projectId Current project ID
445
+ * @param maxStore Max learnings to store (default 5)
446
+ * @returns Number of learnings stored
447
+ */
448
+ async function extractSessionLearnings(entries, sessionId, projectId, maxStore = 5) {
449
+ if (entries.length < 10)
450
+ return 0;
451
+ try {
452
+ const summary = buildSummary(entries);
453
+ // Fetch existing memories for dedup context
454
+ const existingMemories = [];
455
+ try {
456
+ const ms = memory_1.MemoryService.getInstance();
457
+ const rules = ms.loadActiveRules(projectId);
458
+ const all = [...rules.preferences, ...rules.corrections, ...rules.failures, ...rules.devops];
459
+ for (const m of all.slice(0, 20)) {
460
+ const val = typeof m.value === 'object' ? (m.value?.content || JSON.stringify(m.value)) : String(m.value);
461
+ existingMemories.push(truncate(val, 80));
462
+ }
463
+ }
464
+ catch {
465
+ // Non-critical — extraction can proceed without dedup context
466
+ }
467
+ const learnings = await (0, llm_classifier_1.extractSessionLearningsWithLLM)(summary, existingMemories);
468
+ if (learnings === null) {
469
+ logFn('event-processor', 'Session extraction requires ANTHROPIC_API_KEY. Set it to enable learning from long sessions.');
470
+ return 0;
471
+ }
472
+ if (learnings.length === 0)
473
+ return 0;
474
+ let stored = 0;
475
+ for (const learning of learnings) {
476
+ if (stored >= maxStore)
477
+ break;
478
+ // Dedup against existing memories
479
+ const existing = (0, shared_1.searchExisting)(learning.content.substring(0, 100));
480
+ if ((0, shared_1.isDuplicate)(learning.content, existing))
481
+ continue;
482
+ (0, shared_1.storeMemory)(learning.content, learning.type, projectId, learning.confidence);
483
+ stored++;
484
+ logFn('event-processor', `Session extraction: ${learning.type} — ${truncate(learning.content, 60)}`);
485
+ }
486
+ return stored;
487
+ }
488
+ catch (err) {
489
+ logFn('event-processor', `extractSessionLearnings error: ${(0, shared_1.safeErrorMessage)(err)}`);
490
+ return 0;
491
+ }
492
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.20.5",
3
+ "version": "0.20.6",
4
4
  "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,6 +27,10 @@ If the user states a preference ("I prefer tabs", "use functional style"), call
27
27
 
28
28
  If a command fails or you need to backtrack, the failure is captured automatically. You don't need to store it manually.
29
29
 
30
+ ## When the user says "recall", "remember", or "store this"
31
+
32
+ ALWAYS use `recall_store_memory`. These keywords mean the user wants Claude Recall specifically — not any other memory system.
33
+
30
34
  ## Before making decisions
31
35
 
32
36
  Call `recall_search_memory` with relevant keywords to check for existing project knowledge before choosing approaches, tools, or conventions.