claude-recall 0.21.0 → 0.21.1

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-10.
11
+ Auto-generated from 5 memories. Last updated: 2026-04-11.
12
12
 
13
13
  ## Rules
14
14
 
15
- - Session test preference 1775856590823
16
- - Test preference 1775856590766-2
17
- - Test preference 1775856590766-1
18
- - Test preference 1775856590766-0
15
+ - Session test preference 1775896807164
16
+ - Test preference 1775896807117-2
17
+ - Test preference 1775896807117-1
18
+ - Test preference 1775896807117-0
19
19
  - Test memory content
20
20
 
21
21
  ---
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "topicId": "preferences",
3
- "sourceHash": "bc008faff7f86df3f887d5949481aad102da2261a2872bb1b030bc4279eee9fd",
3
+ "sourceHash": "47589a75902fabe04a4aab7d8c89e3cdd26c4c1cf7b6e3086a7ea3186413abe4",
4
4
  "memoryCount": 5,
5
- "generatedAt": "2026-04-10T21:29:50.846Z",
5
+ "generatedAt": "2026-04-11T08:40:07.182Z",
6
6
  "memoryKeys": [
7
- "memory_1775856590824_6gpdsqsh0",
8
- "memory_1775856590800_fv6xg43wx",
9
- "memory_1775856590782_kwl352n83",
10
- "memory_1775856590768_crx4xtq3i",
11
- "memory_1775856590726_t9ap1te0i"
7
+ "memory_1775896807165_8gx2muuz6",
8
+ "memory_1775896807142_2zmd3oc2x",
9
+ "memory_1775896807130_nsc3d9wky",
10
+ "memory_1775896807118_3qvm34ozn",
11
+ "memory_1775896807081_saak1fkqp"
12
12
  ]
13
13
  }
@@ -1,10 +1,67 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MemoryTools = void 0;
4
+ exports.formatRuleValue = formatRuleValue;
4
5
  const config_1 = require("../../services/config");
5
6
  const search_monitor_1 = require("../../services/search-monitor");
6
7
  const skill_generator_1 = require("../../services/skill-generator");
7
8
  const outcome_storage_1 = require("../../services/outcome-storage");
9
+ /**
10
+ * Render any memory.value shape as a readable string for load_rules output.
11
+ *
12
+ * Memory values land in the DB in several historical shapes. The previous
13
+ * rendering used `m.value.content || m.value.value || JSON.stringify(m.value)`
14
+ * which short-circuited on truthy non-string objects, producing "[object Object]"
15
+ * when string interpolation eventually called toString() on the returned object.
16
+ *
17
+ * Rules:
18
+ * 1. strings/numbers pass through (or coerce)
19
+ * 2. null/undefined → empty string
20
+ * 3. objects: prefer the first STRING field in order: content, value, title, description
21
+ * (only string — non-string `content` falls through to title)
22
+ * 4. nested failure shapes: extract `what_failed` (top-level or under `content`)
23
+ * 5. last resort: truncated JSON.stringify (never raw object)
24
+ *
25
+ * Exported for direct unit testing in tests/unit/format-rule-value.test.ts.
26
+ */
27
+ function formatRuleValue(value) {
28
+ if (value == null)
29
+ return '';
30
+ if (typeof value === 'string')
31
+ return value;
32
+ if (typeof value !== 'object')
33
+ return String(value);
34
+ const v = value;
35
+ // Prefer the first non-empty string field. Order matters:
36
+ // - `content` covers legacy hook failures and promoted lessons (lesson text)
37
+ // - `value` covers preference shape
38
+ // - `title` covers tool-outcome-watcher failures whose `content` is a nested object
39
+ // - `description` is a last-ditch human label
40
+ for (const field of ['content', 'value', 'title', 'description']) {
41
+ const candidate = v[field];
42
+ if (typeof candidate === 'string' && candidate.trim()) {
43
+ return candidate;
44
+ }
45
+ }
46
+ // Nested failure object — extract what_failed if present
47
+ if (typeof v.what_failed === 'string' && v.what_failed.trim()) {
48
+ return v.what_failed;
49
+ }
50
+ if (v.content && typeof v.content === 'object') {
51
+ const inner = v.content;
52
+ if (typeof inner.what_failed === 'string' && inner.what_failed.trim()) {
53
+ return inner.what_failed;
54
+ }
55
+ }
56
+ // Last resort: truncated JSON. Never return a raw object.
57
+ try {
58
+ const json = JSON.stringify(value);
59
+ return json.length > 200 ? json.substring(0, 200) + '…' : json;
60
+ }
61
+ catch {
62
+ return String(value);
63
+ }
64
+ }
8
65
  class MemoryTools {
9
66
  constructor(memoryService, logger, onMemoryChanged) {
10
67
  this.memoryService = memoryService;
@@ -287,7 +344,7 @@ class MemoryTools {
287
344
  const sections = [];
288
345
  if (rules.preferences.length > 0) {
289
346
  sections.push('## Preferences\n' + rules.preferences.map(m => {
290
- const val = typeof m.value === 'object' ? (m.value.content || m.value.value || JSON.stringify(m.value)) : m.value;
347
+ const val = formatRuleValue(m.value);
291
348
  // Only show key prefix if it's a meaningful name (not auto-generated)
292
349
  const key = m.preference_key || m.key || '';
293
350
  const isAutoKey = key.startsWith('memory_') || key.startsWith('auto_') || key.startsWith('pref_');
@@ -296,7 +353,7 @@ class MemoryTools {
296
353
  }
297
354
  if (rules.corrections.length > 0) {
298
355
  sections.push('## Corrections\n' + rules.corrections.map(m => {
299
- const val = typeof m.value === 'object' ? (m.value.content || m.value.value || JSON.stringify(m.value)) : m.value;
356
+ const val = formatRuleValue(m.value);
300
357
  const isPromoted = m.key.startsWith('promoted_') || m.value?.source === 'promotion-engine';
301
358
  const evidence = isPromoted && m.value?.evidence_count ? ` (learned from ${m.value.evidence_count} observations)` : '';
302
359
  return isPromoted ? `- [promoted lesson] ${val}${evidence}` : `- ${val}`;
@@ -308,21 +365,21 @@ class MemoryTools {
308
365
  const regularFailures = rules.failures.filter(m => !m.key.startsWith('promoted_') && m.value?.source !== 'promotion-engine');
309
366
  if (promotedLessons.length > 0) {
310
367
  sections.push('## Promoted Lessons (learned from repeated outcomes)\n' + promotedLessons.map(m => {
311
- const val = typeof m.value === 'object' ? (m.value.content || m.value.value || JSON.stringify(m.value)) : m.value;
368
+ const val = formatRuleValue(m.value);
312
369
  const evidence = m.value?.evidence_count ? ` (seen ${m.value.evidence_count}x)` : '';
313
370
  return `- ${val}${evidence}`;
314
371
  }).join('\n'));
315
372
  }
316
373
  if (regularFailures.length > 0) {
317
374
  sections.push('## Failures\n' + regularFailures.map(m => {
318
- const val = typeof m.value === 'object' ? (m.value.content || m.value.value || JSON.stringify(m.value)) : m.value;
375
+ const val = formatRuleValue(m.value);
319
376
  return `- ${val}`;
320
377
  }).join('\n'));
321
378
  }
322
379
  }
323
380
  if (rules.devops.length > 0) {
324
381
  sections.push('## DevOps Rules\n' + rules.devops.map(m => {
325
- const val = typeof m.value === 'object' ? (m.value.content || m.value.value || JSON.stringify(m.value)) : m.value;
382
+ const val = formatRuleValue(m.value);
326
383
  return `- ${val}`;
327
384
  }).join('\n'));
328
385
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
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": {