claude-recall 0.22.1 → 0.22.2

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,14 +8,14 @@ source: claude-recall
8
8
 
9
9
  # Preferences
10
10
 
11
- Auto-generated from 5 memories. Last updated: 2026-04-11.
11
+ Auto-generated from 5 memories. Last updated: 2026-04-14.
12
12
 
13
13
  ## Rules
14
14
 
15
- - Session test preference 1775902182248
16
- - Test preference 1775902182184-2
17
- - Test preference 1775902182184-1
18
- - Test preference 1775902182184-0
15
+ - Session test preference 1776172131422
16
+ - Test preference 1776172131359-2
17
+ - Test preference 1776172131359-1
18
+ - Test preference 1776172131359-0
19
19
  - Test memory content
20
20
 
21
21
  ---
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "topicId": "preferences",
3
- "sourceHash": "a383c0d6502023d06954eb49fcab8886dc5181d5e59666f6c74a381221e44f87",
3
+ "sourceHash": "37e19c67668ff4fdd21019d4837a36dc368138b8e4299bcff235fdaf84b5f028",
4
4
  "memoryCount": 5,
5
- "generatedAt": "2026-04-11T10:09:42.271Z",
5
+ "generatedAt": "2026-04-14T13:08:51.442Z",
6
6
  "memoryKeys": [
7
- "memory_1775902182249_x5rzzep7s",
8
- "memory_1775902182226_9uo2kaw57",
9
- "memory_1775902182211_pl5fzrb85",
10
- "memory_1775902182185_q6f9widp3",
11
- "memory_1775902182147_olowsptz3"
7
+ "memory_1776172131423_zdtbhf2vw",
8
+ "memory_1776172131396_d2s4cuyls",
9
+ "memory_1776172131378_1r9w9r7lq",
10
+ "memory_1776172131363_gqgkc4o6s",
11
+ "memory_1776172131318_dbwsryce8"
12
12
  ]
13
13
  }
@@ -274,6 +274,10 @@ class MemoryTools {
274
274
  ? metadata.type
275
275
  : 'preference';
276
276
  const key = `memory_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
277
+ const preferenceKey = typeof metadata?.preference_key === 'string' && metadata.preference_key.length > 0
278
+ ? metadata.preference_key
279
+ : undefined;
280
+ const isOverride = metadata?.isOverride === true;
277
281
  this.memoryService.store({
278
282
  key,
279
283
  value: {
@@ -288,8 +292,17 @@ class MemoryTools {
288
292
  projectId: scope === 'project' ? context.projectId : undefined,
289
293
  timestamp: context.timestamp,
290
294
  scope: scope || null
291
- }
295
+ },
296
+ preferenceKey,
297
+ isOverride
292
298
  });
299
+ // If this store overrides an existing rule, mark previous active rules with
300
+ // the same preference_key as superseded and surface their keys so the agent
301
+ // knows to ignore the stale text sitting higher up in its context.
302
+ let supersededKeys = [];
303
+ if (isOverride && preferenceKey) {
304
+ supersededKeys = this.memoryService.supersedeByPreferenceKey(preferenceKey, key, { sessionId: context.sessionId, projectId: context.projectId, timestamp: context.timestamp });
305
+ }
293
306
  this.logger.info('MemoryTools', 'Memory stored successfully', {
294
307
  key,
295
308
  type: detectedType,
@@ -320,6 +333,10 @@ class MemoryTools {
320
333
  activeRule: `Stored as active rule:\n- ${content}`,
321
334
  type: detectedType,
322
335
  _directive: 'Apply this rule immediately. No need to call load_rules again.',
336
+ ...(supersededKeys.length > 0 && {
337
+ supersededKeys,
338
+ _supersessionNotice: `Superseded ${supersededKeys.length} prior rule(s) for preference_key="${preferenceKey}". Ignore any earlier text from these rules still in your context: ${supersededKeys.join(', ')}`
339
+ }),
323
340
  ...(skillResults.length > 0 && {
324
341
  _skillsGenerated: skillResults
325
342
  .filter(r => r.action === 'created' || r.action === 'updated')
@@ -383,11 +400,21 @@ class MemoryTools {
383
400
  return `- ${val}`;
384
401
  }).join('\n'));
385
402
  }
386
- // Add compliance section for rules loaded frequently but never cited
403
+ // Add compliance section for rules loaded frequently but never cited.
404
+ // Cap at top 10 by load_count so load_rules stays slim — this diagnostic
405
+ // list was previously unbounded and could dominate the payload.
406
+ const RULE_HEALTH_CAP = 10;
387
407
  const compliance = this.memoryService.getComplianceReport(projectId || context.projectId);
388
- const lowCompliance = compliance.rules.filter(r => r.load_count >= 5 && r.cite_count === 0);
408
+ const allLowCompliance = compliance.rules.filter(r => r.load_count >= 5 && r.cite_count === 0);
409
+ const lowCompliance = [...allLowCompliance]
410
+ .sort((a, b) => (b.load_count || 0) - (a.load_count || 0))
411
+ .slice(0, RULE_HEALTH_CAP);
412
+ const hiddenCount = allLowCompliance.length - lowCompliance.length;
389
413
  if (lowCompliance.length > 0) {
390
- sections.push('## Rule Health\nThese rules are loaded frequently but never cited — consider rewording or removing:\n' +
414
+ const heading = hiddenCount > 0
415
+ ? `## Rule Health (top ${RULE_HEALTH_CAP} of ${allLowCompliance.length})\nThese rules are loaded frequently but never cited — consider rewording or removing. ${hiddenCount} more hidden; run \`npx claude-recall outcomes\` to see all.`
416
+ : '## Rule Health\nThese rules are loaded frequently but never cited — consider rewording or removing:';
417
+ sections.push(heading + '\n' +
391
418
  lowCompliance.map(r => {
392
419
  let val;
393
420
  if (typeof r.value === 'string') {
@@ -562,6 +562,23 @@ class MemoryStorage {
562
562
  const stmt = this.db.prepare(`UPDATE memories SET ${setClause} WHERE key = ?`);
563
563
  stmt.run(...values, key);
564
564
  }
565
+ /**
566
+ * Get active memories (any type) that share the given preference_key.
567
+ * Used by store_memory's override path — a user can override a rule of any
568
+ * type (devops, correction, preference) as long as it was stored with a
569
+ * preference_key.
570
+ */
571
+ getActiveByPreferenceKeyAnyType(preferenceKey, projectId) {
572
+ let query = 'SELECT * FROM memories WHERE preference_key = ? AND is_active = 1';
573
+ const params = [preferenceKey];
574
+ if (projectId) {
575
+ query += ' AND (project_id = ? OR project_id IS NULL)';
576
+ params.push(projectId);
577
+ }
578
+ query += ' ORDER BY timestamp DESC';
579
+ const rows = this.db.prepare(query).all(...params);
580
+ return rows.map(row => this.rowToMemory(row));
581
+ }
565
582
  /**
566
583
  * Get preferences by preference key
567
584
  */
@@ -43,7 +43,9 @@ class MemoryService {
43
43
  file_path: request.context?.filePath,
44
44
  timestamp: request.context?.timestamp || Date.now(),
45
45
  relevance_score: request.relevanceScore || 1.0,
46
- scope: scope
46
+ scope: scope,
47
+ preference_key: request.preferenceKey,
48
+ is_active: true
47
49
  };
48
50
  this.storage.save(memory);
49
51
  this.logger.logMemoryOperation('STORE', {
@@ -303,6 +305,30 @@ class MemoryService {
303
305
  throw error;
304
306
  }
305
307
  }
308
+ /**
309
+ * Mark all currently-active memories with the given preference_key as superseded by newKey.
310
+ * Returns the list of superseded keys so callers can surface them to the agent — this
311
+ * closes the "I stored an override but the old rule is still in my context" gap.
312
+ */
313
+ supersedeByPreferenceKey(preferenceKey, newKey, context) {
314
+ if (!preferenceKey || !newKey)
315
+ return [];
316
+ const superseded = [];
317
+ try {
318
+ const pid = context.projectId || this.config.getProjectId();
319
+ const existing = this.storage.getActiveByPreferenceKeyAnyType(preferenceKey, pid);
320
+ for (const prev of existing) {
321
+ if (prev.key !== newKey) {
322
+ this.storage.markSuperseded(prev.key, newKey);
323
+ superseded.push(prev.key);
324
+ }
325
+ }
326
+ }
327
+ catch (error) {
328
+ this.logger.logServiceError('MemoryService', 'supersedeByPreferenceKey', error, { preferenceKey, newKey });
329
+ }
330
+ return superseded;
331
+ }
306
332
  /**
307
333
  * Mark existing preferences as superseded
308
334
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.22.1",
3
+ "version": "0.22.2",
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": {