claude-recall 0.20.16 → 0.21.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.
@@ -12,10 +12,10 @@ Auto-generated from 5 memories. Last updated: 2026-04-10.
12
12
 
13
13
  ## Rules
14
14
 
15
- - Session test preference 1775841028541
16
- - Test preference 1775841028479-2
17
- - Test preference 1775841028479-1
18
- - Test preference 1775841028479-0
15
+ - Session test preference 1775856590823
16
+ - Test preference 1775856590766-2
17
+ - Test preference 1775856590766-1
18
+ - Test preference 1775856590766-0
19
19
  - Test memory content
20
20
 
21
21
  ---
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "topicId": "preferences",
3
- "sourceHash": "ffe036415b05cb18e9c3e65a38a2e7dedbf02dfd5463c14a03184a9c93c75799",
3
+ "sourceHash": "bc008faff7f86df3f887d5949481aad102da2261a2872bb1b030bc4279eee9fd",
4
4
  "memoryCount": 5,
5
- "generatedAt": "2026-04-10T17:10:28.567Z",
5
+ "generatedAt": "2026-04-10T21:29:50.846Z",
6
6
  "memoryKeys": [
7
- "memory_1775841028543_yc9163qfd",
8
- "memory_1775841028515_8bjn9exsc",
9
- "memory_1775841028500_gch5kvqxl",
10
- "memory_1775841028480_n2evtiegj",
11
- "memory_1775841028436_w06waet05"
7
+ "memory_1775856590824_6gpdsqsh0",
8
+ "memory_1775856590800_fv6xg43wx",
9
+ "memory_1775856590782_kwl352n83",
10
+ "memory_1775856590768_crx4xtq3i",
11
+ "memory_1775856590726_t9ap1te0i"
12
12
  ]
13
13
  }
package/README.md CHANGED
@@ -180,6 +180,24 @@ claude-recall import backup.json # Import memories from JSON
180
180
  claude-recall clear --force # Delete all memories (irreversible)
181
181
  ```
182
182
 
183
+ ### Task Checkpoints
184
+
185
+ Persistent "where I left off" snapshots — one per project, replaces previous on save. Not loaded as a rule; `load_rules` only hints that one exists.
186
+
187
+ ```bash
188
+ claude-recall checkpoint save \
189
+ --completed "inference layer, domain layer" \
190
+ --remaining "wire server.js, strip 3GPP URNs" \
191
+ --blockers "none" \
192
+ --notes "see inference/README.md"
193
+
194
+ claude-recall checkpoint load # Show the latest checkpoint
195
+ claude-recall checkpoint load --json # Machine-readable
196
+ claude-recall checkpoint clear # Delete the checkpoint
197
+ ```
198
+
199
+ Agents can also save/load checkpoints via MCP tools (`mcp__claude-recall__save_checkpoint` / `mcp__claude-recall__load_checkpoint`) or Pi tools (`recall_save_checkpoint` / `recall_load_checkpoint`).
200
+
183
201
  ### Troubleshooting
184
202
 
185
203
  ```bash
@@ -246,6 +264,11 @@ claude-recall outcomes --section stats # Retrieval/helpfulness stats
246
264
  claude-recall outcomes --limit 20 # More items per section
247
265
  claude-recall monitor # Memory search monitoring stats
248
266
 
267
+ # ── Task Checkpoints ────────────────────────────────────────────────
268
+ claude-recall checkpoint save --completed <text> --remaining <text> [--blockers <text>] [--notes <text>] [--project <id>]
269
+ claude-recall checkpoint load [--project <id>] [--json]
270
+ claude-recall checkpoint clear [--project <id>]
271
+
249
272
  # ── Skills ───────────────────────────────────────────────────────────
250
273
  claude-recall skills generate # Generate skills from memories
251
274
  claude-recall skills generate --dry-run # Preview without writing
@@ -500,6 +500,80 @@ class ClaudeRecallCLI {
500
500
  process.exit(1);
501
501
  }
502
502
  }
503
+ /**
504
+ * Save a task checkpoint for the current (or specified) project.
505
+ */
506
+ checkpointSave(opts) {
507
+ try {
508
+ const projectId = opts.project || config_1.ConfigService.getInstance().getProjectId();
509
+ this.memoryService.saveCheckpoint(projectId, {
510
+ completed: opts.completed,
511
+ remaining: opts.remaining,
512
+ blockers: opts.blockers || 'none',
513
+ notes: opts.notes,
514
+ });
515
+ console.log(`\n✅ Checkpoint saved for project: ${projectId}\n`);
516
+ console.log(` Completed: ${opts.completed}`);
517
+ console.log(` Remaining: ${opts.remaining}`);
518
+ console.log(` Blockers: ${opts.blockers || 'none'}`);
519
+ if (opts.notes)
520
+ console.log(` Notes: ${opts.notes}`);
521
+ console.log('');
522
+ }
523
+ catch (error) {
524
+ console.error('❌ Checkpoint save failed:', error);
525
+ process.exit(1);
526
+ }
527
+ }
528
+ /**
529
+ * Load the latest task checkpoint for the current (or specified) project.
530
+ */
531
+ checkpointLoad(opts) {
532
+ try {
533
+ const projectId = opts.project || config_1.ConfigService.getInstance().getProjectId();
534
+ const checkpoint = this.memoryService.loadCheckpoint(projectId);
535
+ if (!checkpoint) {
536
+ console.log(`\nNo checkpoint found for project: ${projectId}\n`);
537
+ return;
538
+ }
539
+ if (opts.json) {
540
+ console.log(JSON.stringify({ projectId, ...checkpoint }, null, 2));
541
+ return;
542
+ }
543
+ const updatedDate = new Date(checkpoint.updated_at).toLocaleString();
544
+ console.log(`\n📌 Checkpoint for project: ${projectId}`);
545
+ console.log(` Updated: ${updatedDate}\n`);
546
+ console.log(` Completed: ${checkpoint.completed}`);
547
+ console.log(` Remaining: ${checkpoint.remaining}`);
548
+ console.log(` Blockers: ${checkpoint.blockers}`);
549
+ if (checkpoint.notes)
550
+ console.log(` Notes: ${checkpoint.notes}`);
551
+ console.log('');
552
+ }
553
+ catch (error) {
554
+ console.error('❌ Checkpoint load failed:', error);
555
+ process.exit(1);
556
+ }
557
+ }
558
+ /**
559
+ * Delete the task checkpoint for the current (or specified) project.
560
+ */
561
+ checkpointClear(opts) {
562
+ try {
563
+ const projectId = opts.project || config_1.ConfigService.getInstance().getProjectId();
564
+ const deleted = this.memoryService.deleteCheckpoint(projectId);
565
+ if (deleted) {
566
+ console.log(`\n✅ Checkpoint cleared for project: ${projectId}\n`);
567
+ }
568
+ else {
569
+ console.log(`\nNo checkpoint to clear for project: ${projectId}\n`);
570
+ }
571
+ }
572
+ catch (error) {
573
+ console.error('❌ Checkpoint clear failed:', error);
574
+ process.exit(1);
575
+ }
576
+ }
503
577
  /**
504
578
  * Show system status
505
579
  */
@@ -1394,6 +1468,48 @@ async function main() {
1394
1468
  await cli.clear(options);
1395
1469
  process.exit(0);
1396
1470
  });
1471
+ // Checkpoint command group
1472
+ const checkpoint = program
1473
+ .command('checkpoint')
1474
+ .description('Save and load task checkpoints (work-in-progress snapshots)');
1475
+ checkpoint
1476
+ .command('save')
1477
+ .description('Save a task checkpoint for the current project (replaces previous)')
1478
+ .requiredOption('--completed <text>', 'What is done')
1479
+ .requiredOption('--remaining <text>', 'What is left to do')
1480
+ .option('--blockers <text>', 'Current blockers (default: "none")')
1481
+ .option('--notes <text>', 'Free-form notes, file refs, etc.')
1482
+ .option('--project <id>', 'Project ID (default: current)')
1483
+ .action((options) => {
1484
+ const cli = new ClaudeRecallCLI(program.opts());
1485
+ cli.checkpointSave({
1486
+ project: options.project,
1487
+ completed: options.completed,
1488
+ remaining: options.remaining,
1489
+ blockers: options.blockers,
1490
+ notes: options.notes,
1491
+ });
1492
+ process.exit(0);
1493
+ });
1494
+ checkpoint
1495
+ .command('load')
1496
+ .description('Load the latest task checkpoint for the current project')
1497
+ .option('--project <id>', 'Project ID (default: current)')
1498
+ .option('--json', 'Output as JSON')
1499
+ .action((options) => {
1500
+ const cli = new ClaudeRecallCLI(program.opts());
1501
+ cli.checkpointLoad({ project: options.project, json: options.json });
1502
+ process.exit(0);
1503
+ });
1504
+ checkpoint
1505
+ .command('clear')
1506
+ .description('Delete the task checkpoint for the current project')
1507
+ .option('--project <id>', 'Project ID (default: current)')
1508
+ .action((options) => {
1509
+ const cli = new ClaudeRecallCLI(program.opts());
1510
+ cli.checkpointClear({ project: options.project });
1511
+ process.exit(0);
1512
+ });
1397
1513
  // Status command
1398
1514
  program
1399
1515
  .command('status')
@@ -162,7 +162,34 @@ class MemoryTools {
162
162
  required: ['id']
163
163
  },
164
164
  handler: this.handleDeleteMemory.bind(this)
165
- }
165
+ },
166
+ {
167
+ name: 'mcp__claude-recall__save_checkpoint',
168
+ description: 'Save a task checkpoint — a structured snapshot of work in progress (completed/remaining/blockers/notes). Replaces any previous checkpoint for this project. Call when ending a work session or pausing on a task.',
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: {
172
+ completed: { type: 'string', description: 'What has been finished in this work stream' },
173
+ remaining: { type: 'string', description: 'What is left to do' },
174
+ blockers: { type: 'string', description: 'Current blockers (use "none" if none)' },
175
+ notes: { type: 'string', description: 'Optional free-form notes, file references, etc.' },
176
+ projectId: { type: 'string', description: 'Optional project ID override. Defaults to current project.' },
177
+ },
178
+ required: ['completed', 'remaining', 'blockers'],
179
+ },
180
+ handler: this.handleSaveCheckpoint.bind(this),
181
+ },
182
+ {
183
+ name: 'mcp__claude-recall__load_checkpoint',
184
+ description: 'Load the latest task checkpoint for the current project. Returns null if none exists. Call at the start of a session to recall where you left off — load_rules will hint when one exists.',
185
+ inputSchema: {
186
+ type: 'object',
187
+ properties: {
188
+ projectId: { type: 'string', description: 'Optional project ID override. Defaults to current project.' },
189
+ },
190
+ },
191
+ handler: this.handleLoadCheckpoint.bind(this),
192
+ },
166
193
  ];
167
194
  }
168
195
  /**
@@ -349,15 +376,30 @@ class MemoryTools {
349
376
  catch {
350
377
  // Non-critical
351
378
  }
379
+ // Checkpoint hint — surface existence without dumping content
380
+ let checkpointHint = '';
381
+ try {
382
+ const project = projectId || context.projectId || config_1.ConfigService.getInstance().getProjectId();
383
+ if (this.memoryService.hasCheckpoint(project)) {
384
+ const cp = this.memoryService.loadCheckpoint(project);
385
+ if (cp) {
386
+ const updatedDate = new Date(cp.updated_at).toLocaleString();
387
+ checkpointHint = `📌 You have an unfinished task checkpoint from ${updatedDate} — call \`load_checkpoint\` to see completed/remaining/blockers before starting work.\n\n`;
388
+ }
389
+ }
390
+ }
391
+ catch {
392
+ // Non-critical
393
+ }
352
394
  let rulesText;
353
395
  if (sections.length > 0) {
354
396
  const body = sections.join('\n\n');
355
397
  rulesText = directive
356
- ? `${directive}\n\n---\n\n${body}`
357
- : body;
398
+ ? `${directive}\n\n${checkpointHint}---\n\n${body}`
399
+ : `${checkpointHint}${body}`;
358
400
  }
359
401
  else {
360
- rulesText = 'No active rules found. This may be a new project.';
402
+ rulesText = checkpointHint || 'No active rules found. This may be a new project.';
361
403
  }
362
404
  return {
363
405
  rules: rulesText,
@@ -462,6 +504,45 @@ class MemoryTools {
462
504
  throw error;
463
505
  }
464
506
  }
507
+ async handleSaveCheckpoint(input, context) {
508
+ try {
509
+ const { completed, remaining, blockers, notes, projectId } = input;
510
+ if (typeof completed !== 'string' || typeof remaining !== 'string' || typeof blockers !== 'string') {
511
+ throw new Error('completed, remaining, and blockers are required string fields');
512
+ }
513
+ const project = projectId || config_1.ConfigService.getInstance().getProjectId();
514
+ this.memoryService.saveCheckpoint(project, { completed, remaining, blockers, notes });
515
+ return {
516
+ success: true,
517
+ projectId: project,
518
+ message: `Checkpoint saved for project: ${project}`,
519
+ };
520
+ }
521
+ catch (error) {
522
+ this.logger.error('MemoryTools', 'Failed to save checkpoint', error);
523
+ throw error;
524
+ }
525
+ }
526
+ async handleLoadCheckpoint(input, context) {
527
+ try {
528
+ const { projectId } = input || {};
529
+ const project = projectId || config_1.ConfigService.getInstance().getProjectId();
530
+ const checkpoint = this.memoryService.loadCheckpoint(project);
531
+ if (!checkpoint) {
532
+ return { found: false, projectId: project };
533
+ }
534
+ return {
535
+ found: true,
536
+ projectId: project,
537
+ ...checkpoint,
538
+ updated_at_iso: new Date(checkpoint.updated_at).toISOString(),
539
+ };
540
+ }
541
+ catch (error) {
542
+ this.logger.error('MemoryTools', 'Failed to load checkpoint', error);
543
+ throw error;
544
+ }
545
+ }
465
546
  getTools() {
466
547
  return this.tools;
467
548
  }
@@ -329,6 +329,51 @@ class MemoryStorage {
329
329
  // This ensures that other processes (like CLI) can see the changes immediately
330
330
  this.db.pragma('wal_checkpoint(TRUNCATE)');
331
331
  }
332
+ // --- Task checkpoint methods ---
333
+ checkpointKey(projectId) {
334
+ return `task-checkpoint:${projectId}`;
335
+ }
336
+ saveCheckpoint(projectId, input) {
337
+ const checkpoint = {
338
+ completed: input.completed,
339
+ remaining: input.remaining,
340
+ blockers: input.blockers,
341
+ notes: input.notes,
342
+ updated_at: Date.now(),
343
+ };
344
+ // Use a deterministic key per project so INSERT OR REPLACE replaces the previous one.
345
+ // Direct INSERT OR REPLACE bypasses fuzzy/hash dedup since they check `key != ?`.
346
+ const stmt = this.db.prepare(`
347
+ INSERT OR REPLACE INTO memories
348
+ (key, value, type, project_id, file_path, timestamp, relevance_score, access_count,
349
+ preference_key, is_active, superseded_by, superseded_at, confidence_score, sophistication_level, scope, content_hash)
350
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
351
+ `);
352
+ const value = JSON.stringify(checkpoint);
353
+ const contentHash = this.computeContentHash(checkpoint, 'task-checkpoint');
354
+ stmt.run(this.checkpointKey(projectId), value, 'task-checkpoint', projectId, null, checkpoint.updated_at, 1.0, 0, null, 1, null, null, null, 1, null, contentHash);
355
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
356
+ }
357
+ loadCheckpoint(projectId) {
358
+ const row = this.db.prepare('SELECT value FROM memories WHERE key = ? AND type = ?').get(this.checkpointKey(projectId), 'task-checkpoint');
359
+ if (!row)
360
+ return null;
361
+ try {
362
+ return JSON.parse(row.value);
363
+ }
364
+ catch {
365
+ return null;
366
+ }
367
+ }
368
+ hasCheckpoint(projectId) {
369
+ const row = this.db.prepare('SELECT 1 FROM memories WHERE key = ? AND type = ?').get(this.checkpointKey(projectId), 'task-checkpoint');
370
+ return !!row;
371
+ }
372
+ deleteCheckpoint(projectId) {
373
+ const result = this.db.prepare('DELETE FROM memories WHERE key = ? AND type = ?').run(this.checkpointKey(projectId), 'task-checkpoint');
374
+ this.db.pragma('wal_checkpoint(TRUNCATE)');
375
+ return result.changes > 0;
376
+ }
332
377
  retrieve(key) {
333
378
  const stmt = this.db.prepare('SELECT * FROM memories WHERE key = ?');
334
379
  const row = stmt.get(key);
@@ -217,9 +217,21 @@ function default_1(pi) {
217
217
  os.recordRetrieval(m.key);
218
218
  }
219
219
  catch { /* non-critical */ }
220
+ // Checkpoint hint — surface existence without dumping content
221
+ let checkpointHint = '';
222
+ try {
223
+ if (ms.hasCheckpoint(projectId)) {
224
+ const cp = ms.loadCheckpoint(projectId);
225
+ if (cp) {
226
+ const updatedDate = new Date(cp.updated_at).toLocaleString();
227
+ checkpointHint = `📌 You have an unfinished task checkpoint from ${updatedDate} — call \`recall_load_checkpoint\` to see completed/remaining/blockers before starting work.\n\n`;
228
+ }
229
+ }
230
+ }
231
+ catch { /* non-critical */ }
220
232
  const text = body
221
- ? `${LOAD_RULES_DIRECTIVE}\n\n---\n\n${body}`
222
- : 'No active rules found. This may be a new project.';
233
+ ? `${LOAD_RULES_DIRECTIVE}\n\n${checkpointHint}---\n\n${body}`
234
+ : (checkpointHint || 'No active rules found. This may be a new project.');
223
235
  return {
224
236
  content: [{ type: 'text', text }],
225
237
  };
@@ -333,6 +345,73 @@ function default_1(pi) {
333
345
  }
334
346
  },
335
347
  });
348
+ // --- Tool: recall_save_checkpoint ---
349
+ pi.registerTool({
350
+ name: 'recall_save_checkpoint',
351
+ label: 'Save Task Checkpoint',
352
+ description: 'Save a structured snapshot of work in progress (completed/remaining/blockers/notes). Replaces any previous checkpoint for this project. Call when ending a session or pausing on a task.',
353
+ promptSnippet: 'Save a task checkpoint with what is done, what remains, and any blockers',
354
+ parameters: {},
355
+ async execute(_id, params, _signal, _onUpdate, _ctx) {
356
+ try {
357
+ const { completed, remaining, blockers, notes } = params || {};
358
+ if (typeof completed !== 'string' || typeof remaining !== 'string' || typeof blockers !== 'string') {
359
+ return {
360
+ content: [{ type: 'text', text: 'Error: completed, remaining, and blockers are required string fields' }],
361
+ isError: true,
362
+ };
363
+ }
364
+ const ms = memory_1.MemoryService.getInstance();
365
+ ms.saveCheckpoint(projectId, { completed, remaining, blockers, notes });
366
+ return {
367
+ content: [{ type: 'text', text: JSON.stringify({
368
+ success: true,
369
+ projectId,
370
+ message: `Checkpoint saved for project: ${projectId}`,
371
+ }) }],
372
+ };
373
+ }
374
+ catch (err) {
375
+ return {
376
+ content: [{ type: 'text', text: `Failed to save checkpoint: ${err.message}` }],
377
+ isError: true,
378
+ };
379
+ }
380
+ },
381
+ });
382
+ // --- Tool: recall_load_checkpoint ---
383
+ pi.registerTool({
384
+ name: 'recall_load_checkpoint',
385
+ label: 'Load Task Checkpoint',
386
+ description: 'Load the latest task checkpoint for the current project. Returns null if none exists. Call at session start to recall where you left off.',
387
+ promptSnippet: 'Load the saved task checkpoint to see what was in progress',
388
+ parameters: {},
389
+ async execute(_id, _params, _signal, _onUpdate, _ctx) {
390
+ try {
391
+ const ms = memory_1.MemoryService.getInstance();
392
+ const checkpoint = ms.loadCheckpoint(projectId);
393
+ if (!checkpoint) {
394
+ return {
395
+ content: [{ type: 'text', text: JSON.stringify({ found: false, projectId }) }],
396
+ };
397
+ }
398
+ return {
399
+ content: [{ type: 'text', text: JSON.stringify({
400
+ found: true,
401
+ projectId,
402
+ ...checkpoint,
403
+ updated_at_iso: new Date(checkpoint.updated_at).toISOString(),
404
+ }) }],
405
+ };
406
+ }
407
+ catch (err) {
408
+ return {
409
+ content: [{ type: 'text', text: `Failed to load checkpoint: ${err.message}` }],
410
+ isError: true,
411
+ };
412
+ }
413
+ },
414
+ });
336
415
  // --- Tool: recall_delete_memory ---
337
416
  pi.registerTool({
338
417
  name: 'recall_delete_memory',
@@ -527,6 +527,24 @@ class MemoryService {
527
527
  getDatabase() {
528
528
  return this.storage.getDatabase();
529
529
  }
530
+ // --- Task checkpoint methods ---
531
+ saveCheckpoint(projectId, input) {
532
+ this.storage.saveCheckpoint(projectId, input);
533
+ this.logger.info('MemoryService', `Checkpoint saved for project: ${projectId}`);
534
+ }
535
+ loadCheckpoint(projectId) {
536
+ return this.storage.loadCheckpoint(projectId);
537
+ }
538
+ hasCheckpoint(projectId) {
539
+ return this.storage.hasCheckpoint(projectId);
540
+ }
541
+ deleteCheckpoint(projectId) {
542
+ const deleted = this.storage.deleteCheckpoint(projectId);
543
+ if (deleted) {
544
+ this.logger.info('MemoryService', `Checkpoint deleted for project: ${projectId}`);
545
+ }
546
+ return deleted;
547
+ }
530
548
  /**
531
549
  * Check if database is connected
532
550
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.20.16",
3
+ "version": "0.21.0",
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": {