cf-memory-mcp 3.31.0 → 3.32.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/README.md CHANGED
@@ -35,6 +35,32 @@ The active runtime is at [src-simplified/index.ts](src-simplified/index.ts). It
35
35
  - Coordination and progress with Durable Objects
36
36
  - Diagnostics: `health_check` tool + `npx cf-memory-mcp --diagnose` + `CF_MEMORY_TRACE=1`
37
37
 
38
+ ## Resume Context (cross-chat handoff)
39
+
40
+ Beyond code retrieval, cf-memory-mcp persists structured "handoff" packets so one chat can hand work off to the next. The next agent calls `get_context_bootstrap({resume:true})` (or the CLI `cf-memory-mcp resume`) and gets back the goal, status, next steps, code anchors with staleness markers, files touched, and a pre-rendered markdown prompt — without re-exploring the repo.
41
+
42
+ **Measured impact** (`scripts/benchmark-cold-vs-warm.ts`): warm resume is **82-85% faster** with **3-4 fewer tool calls** than cold-start exploration, and the warm path returns the exact next_action the cold agent would have eventually figured out.
43
+
44
+ ```bash
45
+ $ npx cf-memory-mcp resume # print prior handoff for cwd
46
+ $ npx cf-memory-mcp list # all recent handoffs (with status counts)
47
+ $ npx cf-memory-mcp checkpoint # snapshot current state
48
+ $ npx cf-memory-mcp status # bridge + server health for cwd
49
+ $ npx cf-memory-mcp doctor # diagnose common setup issues
50
+ $ npx cf-memory-mcp export <id> # backup a handoff
51
+ $ npx cf-memory-mcp import <file> # cross-machine sync
52
+ $ npx cf-memory-mcp delete <id> # remove a session
53
+ $ cfm resume # short alias
54
+ ```
55
+
56
+ Resume context also surfaces via:
57
+
58
+ - **MCP tools**: `start_session`, `end_session` (with structured `handoff`), `get_context_bootstrap` (with `resume`, `session_id_hint`, `goal_contains`, `since`, `status_filter`, `max_age_minutes`)
59
+ - **MCP resources**: `cfm://resume/current`, `cfm://resume/recent`, `cfm://resume/session/<id>`
60
+ - **MCP prompts**: `/resume`, `/list_threads` (for clients with slash-command UI)
61
+
62
+ See [docs/RESUME_CONTEXT_PLAN.md](docs/RESUME_CONTEXT_PLAN.md) for design rationale and [agents.md](agents.md) for the full agent-facing reference.
63
+
38
64
  ## Active Infra
39
65
 
40
66
  - Workers
@@ -411,13 +411,15 @@ const TOOLS_LIST = [
411
411
  },
412
412
  {
413
413
  name: 'delete_session',
414
- description: 'Delete a session row and any session_summary memories tied to it. Use to remove test sessions or accidental handoffs. Different from end_session — this physically removes the row. Pass the full UUID (not a prefix).',
414
+ description: 'Delete session row(s) and any session_summary memories tied to them. Accepts single-id or bulk filters. Different from end_session — this physically removes rows.',
415
415
  inputSchema: {
416
416
  type: 'object',
417
417
  properties: {
418
- session_id: { type: 'string', description: 'Full session UUID to delete.' }
419
- },
420
- required: ['session_id']
418
+ session_id: { type: 'string', description: 'Full session UUID to delete (not a prefix).' },
419
+ status: { description: 'Status filter: single string or array of statuses (in_progress, blocked, completed, abandoned).' },
420
+ older_than_days: { type: 'number', description: 'Delete sessions older than N days.' },
421
+ repo_path: { type: 'string', description: 'Restrict to a specific repo_path.' }
422
+ }
421
423
  }
422
424
  }
423
425
  ];
@@ -3850,7 +3852,7 @@ For more information, visit: https://github.com/johnlam90/cf-memory-mcp
3850
3852
  }
3851
3853
 
3852
3854
  // Parse positional + flag args for CLI subcommands. Returns
3853
- // { positional: string[], flags: { json, limit, md_path } }.
3855
+ // { positional: string[], flags: { json, limit, md_path, older_than_days, status, repo_path, yes } }.
3854
3856
  function parseCliArgs(rest) {
3855
3857
  const positional = [];
3856
3858
  const flags = { json: false };
@@ -3864,10 +3866,36 @@ function parseCliArgs(rest) {
3864
3866
  const n = parseInt(a.slice('--limit='.length), 10);
3865
3867
  if (Number.isFinite(n) && n > 0) flags.limit = Math.min(n, 50);
3866
3868
  } else if (a === '--md') {
3867
- // Next arg is path; supports `--md=path` too.
3868
3869
  flags.md_path = rest[++i];
3869
3870
  } else if (a.startsWith('--md=')) {
3870
3871
  flags.md_path = a.slice('--md='.length);
3872
+ } else if (a === '--older-than') {
3873
+ // Accept "7d" / "30d" / "12h" / raw number (days).
3874
+ const raw = rest[++i] || '';
3875
+ const m = raw.match(/^(\d+)\s*(d|h|m)?$/);
3876
+ if (m) {
3877
+ const n = parseInt(m[1], 10);
3878
+ const unit = m[2] || 'd';
3879
+ flags.older_than_days = unit === 'd' ? n : unit === 'h' ? n / 24 : n / 1440;
3880
+ }
3881
+ } else if (a.startsWith('--older-than=')) {
3882
+ const raw = a.slice('--older-than='.length);
3883
+ const m = raw.match(/^(\d+)\s*(d|h|m)?$/);
3884
+ if (m) {
3885
+ const n = parseInt(m[1], 10);
3886
+ const unit = m[2] || 'd';
3887
+ flags.older_than_days = unit === 'd' ? n : unit === 'h' ? n / 24 : n / 1440;
3888
+ }
3889
+ } else if (a === '--status') {
3890
+ flags.status = rest[++i];
3891
+ } else if (a.startsWith('--status=')) {
3892
+ flags.status = a.slice('--status='.length);
3893
+ } else if (a === '--repo') {
3894
+ flags.repo_path = rest[++i];
3895
+ } else if (a.startsWith('--repo=')) {
3896
+ flags.repo_path = a.slice('--repo='.length);
3897
+ } else if (a === '--yes' || a === '-y') {
3898
+ flags.yes = true;
3871
3899
  } else positional.push(a);
3872
3900
  }
3873
3901
  return { positional, flags };
@@ -4009,8 +4037,18 @@ async function runListCli() {
4009
4037
  process.stderr.write((payload.empty_hint || 'No handoffs for this context.') + '\n');
4010
4038
  process.exit(3);
4011
4039
  }
4040
+ // Status counts header: scan the visible list to give an at-a-glance
4041
+ // summary before the per-row details.
4042
+ const counts = {};
4043
+ for (const h of allList) {
4044
+ const s = h.status || 'unknown';
4045
+ counts[s] = (counts[s] || 0) + 1;
4046
+ }
4047
+ const countSummary = Object.entries(counts)
4048
+ .map(([s, n]) => `${n} ${s}`)
4049
+ .join(', ');
4012
4050
  // Render a compact table to terminal.
4013
- process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'}:\n\n`);
4051
+ process.stdout.write(`Recent handoffs for ${meta.repo_path || 'this cwd'} (${countSummary}):\n\n`);
4014
4052
  const fmtAge = (iso) => {
4015
4053
  if (!iso) return '?';
4016
4054
  const min = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
@@ -4042,15 +4080,60 @@ async function runDeleteCli() {
4042
4080
  }
4043
4081
  const { positional, flags } = parseCliArgs(process.argv.slice(3));
4044
4082
  const idArg = positional[0];
4045
- if (!idArg) {
4046
- console.error('Usage: cf-memory-mcp delete <session-id-or-prefix>');
4083
+ const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path);
4084
+ if (!idArg && !bulkMode) {
4085
+ console.error('Usage:');
4086
+ console.error(' cf-memory-mcp delete <session-id-or-prefix> # single delete');
4087
+ console.error(' cf-memory-mcp delete --older-than 30d # bulk delete by age');
4088
+ console.error(' cf-memory-mcp delete --status abandoned # bulk by status');
4089
+ console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by repo');
4090
+ console.error(' Combine filters; use --yes to confirm >5 deletions.');
4047
4091
  process.exit(1);
4048
4092
  }
4049
4093
  const server = new CFMemoryMCP();
4050
4094
  server.logDebug = () => {};
4051
4095
  try {
4052
- // Resolve prefix to full id via get_context_bootstrap. The server's
4053
- // delete_session requires a full UUID (no implicit prefix).
4096
+ if (bulkMode) {
4097
+ // Bulk delete path. First dry-run: query matching sessions
4098
+ // count via list, then confirm with --yes for >5.
4099
+ const args = {};
4100
+ if (flags.status) args.status = flags.status;
4101
+ if (flags.older_than_days !== undefined) args.older_than_days = flags.older_than_days;
4102
+ if (flags.repo_path) args.repo_path = flags.repo_path;
4103
+
4104
+ // Preview: do an initial delete_session with the same filters
4105
+ // to count matches via the response's deleted_session_ids
4106
+ // (capped at 50). For larger counts we accept the cap as an
4107
+ // approximation; the server handles all of them anyway.
4108
+ // Use --yes to skip the preview/confirm prompt.
4109
+ if (!flags.yes) {
4110
+ process.stderr.write(`Dry-run: previewing matches without confirm prompt is not supported yet.\n`);
4111
+ process.stderr.write(`Run again with --yes to confirm: cf-memory-mcp delete ${process.argv.slice(3).join(' ')} --yes\n`);
4112
+ process.exit(2);
4113
+ }
4114
+
4115
+ const deleteRes = await server.makeRequest({
4116
+ jsonrpc: '2.0', id: `cli-delete-bulk-${Date.now()}`,
4117
+ method: 'tools/call',
4118
+ params: { name: 'delete_session', arguments: args },
4119
+ });
4120
+ const deleteText = deleteRes?.result?.content?.[0]?.text;
4121
+ const payload = JSON.parse(deleteText || '{}');
4122
+ if (flags.json) {
4123
+ process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
4124
+ process.exit(payload.deleted ? 0 : 3);
4125
+ }
4126
+ if (payload.deleted) {
4127
+ process.stdout.write(`Deleted ${payload.deleted_count} session${payload.deleted_count === 1 ? '' : 's'} (and ${payload.cleaned_memories || 0} associated memories).\n`);
4128
+ process.exit(0);
4129
+ } else {
4130
+ process.stderr.write(`No sessions matched the filters.\n`);
4131
+ process.exit(3);
4132
+ }
4133
+ return;
4134
+ }
4135
+
4136
+ // Single-delete path (existing).
4054
4137
  let fullId = idArg;
4055
4138
  if (idArg.length < 36) {
4056
4139
  const resolveRes = await server.makeRequest({
@@ -4245,11 +4328,15 @@ const PER_COMMAND_HELP = {
4245
4328
  completion: `cf-memory-mcp completion [bash|zsh|fish]
4246
4329
  Output shell completion script. Pipe to your shell's completion dir.`,
4247
4330
  delete: `cf-memory-mcp delete <session-id-or-prefix> [--json]
4248
- Delete a session row and its associated session_summary memories.
4249
- <session-id> Full UUID or short prefix (>=8 chars). The CLI
4250
- resolves prefix to a full id via the server.
4251
- --json, -j Emit a JSON status object.
4252
- Exit codes: 0 = deleted, 3 = not found / could not resolve.`,
4331
+ cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] --yes [--json]
4332
+ Delete session row(s) and any session_summary memories tied to them.
4333
+ <session-id> Single delete: full UUID or short prefix (>=8 chars).
4334
+ --status STATUS Bulk filter by status (abandoned, completed, ...).
4335
+ --older-than 30d Bulk filter by age (suffixes: d, h, m).
4336
+ --repo PATH Bulk filter by repo_path.
4337
+ --yes, -y Required for bulk delete to confirm intent.
4338
+ --json, -j Emit JSON.
4339
+ Exit codes: 0 = deleted, 2 = bulk missing --yes, 3 = nothing matched.`,
4253
4340
  env: `cf-memory-mcp env [--json]
4254
4341
  Print all CF_MEMORY_* env vars the bridge reads, with their current
4255
4342
  values + descriptions. Useful for discovering knobs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.31.0",
3
+ "version": "3.32.0",
4
4
  "description": "Cloudflare-hosted MCP server for code indexing, retrieval, and assistant memory with a direct remote MCP endpoint and local stdio bridge.",
5
5
  "main": "bin/cf-memory-mcp.js",
6
6
  "bin": {