cf-memory-mcp 3.63.0 → 3.65.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.
Files changed (2) hide show
  1. package/bin/cf-memory-mcp.js +122 -10
  2. package/package.json +1 -1
@@ -4145,6 +4145,10 @@ function parseCliArgs(rest) {
4145
4145
  flags.repo_path = rest[++i];
4146
4146
  } else if (a.startsWith('--repo=')) {
4147
4147
  flags.repo_path = a.slice('--repo='.length);
4148
+ } else if (a === '--repo-prefix') {
4149
+ flags.repo_path_prefix = rest[++i];
4150
+ } else if (a.startsWith('--repo-prefix=')) {
4151
+ flags.repo_path_prefix = a.slice('--repo-prefix='.length);
4148
4152
  } else if (a === '--project-id') {
4149
4153
  flags.project_id = rest[++i];
4150
4154
  } else if (a.startsWith('--project-id=')) {
@@ -4706,6 +4710,62 @@ async function runResumeCli() {
4706
4710
  } else {
4707
4711
  process.stdout.write(JSON.stringify(payload.resume_handoff, null, 2) + '\n');
4708
4712
  }
4713
+ // Branch state: if the handoff's repo matches cwd and there
4714
+ // are uncommitted changes (especially in files_touched),
4715
+ // surface them so the agent knows work was interrupted mid-
4716
+ // edit. Quiet when the tree is clean to avoid noise. Skip
4717
+ // entirely when env var disables it.
4718
+ if (process.env.CF_MEMORY_BRANCH_STATE !== 'off') {
4719
+ try {
4720
+ const meta = server.getRepoMetadata();
4721
+ const handoffRepo = payload.resume_handoff?.handoff?.repo_path
4722
+ || payload.resume_handoff?.repo_path;
4723
+ if (meta.repo_path && handoffRepo && meta.repo_path === handoffRepo) {
4724
+ const { execSync } = require('child_process');
4725
+ const dirty = execSync('git status --porcelain', { cwd: meta.repo_path, encoding: 'utf8', timeout: 2000 })
4726
+ .split('\n').filter(Boolean);
4727
+ if (dirty.length > 0) {
4728
+ const filesTouched = Array.isArray(payload.resume_handoff?.handoff?.files_touched)
4729
+ ? payload.resume_handoff.handoff.files_touched
4730
+ .map(ft => typeof ft === 'string' ? ft : ft?.path)
4731
+ .filter(Boolean)
4732
+ : [];
4733
+ const inHandoff = new Set(filesTouched);
4734
+ const matched = dirty.filter(line => {
4735
+ const p = line.slice(3);
4736
+ return inHandoff.has(p);
4737
+ });
4738
+ const otherCount = dirty.length - matched.length;
4739
+ process.stdout.write('\n### Branch state (since handoff was written)\n');
4740
+ if (matched.length > 0) {
4741
+ process.stdout.write(`Mid-edit on ${matched.length} of your files_touched:\n`);
4742
+ for (const line of matched.slice(0, 8)) {
4743
+ process.stdout.write(` ${line}\n`);
4744
+ }
4745
+ if (matched.length > 8) {
4746
+ process.stdout.write(` ... and ${matched.length - 8} more\n`);
4747
+ }
4748
+ }
4749
+ if (otherCount > 0) {
4750
+ process.stdout.write(`Plus ${otherCount} other uncommitted file${otherCount === 1 ? '' : 's'} in the tree (run \`git status\` for full list).\n`);
4751
+ }
4752
+ // Also check if HEAD has advanced past the handoff time.
4753
+ try {
4754
+ const handoffTime = payload.resume_handoff?.ended_at
4755
+ || payload.resume_handoff?.started_at;
4756
+ if (handoffTime) {
4757
+ const since = `--since="${handoffTime}"`;
4758
+ const newCommits = execSync(`git log ${since} --oneline 2>/dev/null | wc -l`, { cwd: meta.repo_path, encoding: 'utf8', timeout: 2000, shell: '/bin/sh' }).trim();
4759
+ const n = parseInt(newCommits, 10);
4760
+ if (Number.isFinite(n) && n > 0) {
4761
+ process.stdout.write(`${n} commit${n === 1 ? '' : 's'} on this branch since handoff (\`git log --since="${handoffTime}"\` to see them).\n`);
4762
+ }
4763
+ }
4764
+ } catch (_) { /* commit-count is best-effort */ }
4765
+ }
4766
+ }
4767
+ } catch (_) { /* git unavailable or non-repo — silent */ }
4768
+ }
4709
4769
  // --include-chain N: walk parent_session_id back N times and
4710
4770
  // append each parent's resume_prompt inline so the agent sees
4711
4771
  // the full thread arc in one output.
@@ -4733,12 +4793,32 @@ async function runResumeCli() {
4733
4793
  }
4734
4794
  }
4735
4795
  if (Array.isArray(payload.recent_handoffs) && payload.recent_handoffs.length > 1) {
4736
- process.stdout.write('\n---\nOther recent handoffs:\n');
4737
- for (const h of payload.recent_handoffs.slice(0, 4)) {
4738
- const shortId = (h.session_id || '').slice(0, 8);
4739
- process.stdout.write(` ${shortId} [${h.status || '?'}] ${h.goal || ''}\n`);
4796
+ // Dedupe: skip the primary handoff (already shown) and
4797
+ // collapse near-duplicate auto-checkpoints entries that
4798
+ // share normalized (goal, branch, status). Keep the most
4799
+ // recent occurrence of each unique key. This removes
4800
+ // visible noise from 30+ in_progress sessions all named
4801
+ // "Re-verify enrichment+freshness" on the same branch.
4802
+ const primaryId = payload.resume_handoff?.session_id;
4803
+ const norm = (s) => (s || '').toLowerCase().replace(/\s+/g, ' ').trim();
4804
+ const seen = new Set();
4805
+ const unique = [];
4806
+ for (const h of payload.recent_handoffs) {
4807
+ if (h.session_id === primaryId) continue;
4808
+ const key = `${norm(h.goal)}|${norm(h.branch)}|${h.status || ''}`;
4809
+ if (seen.has(key)) continue;
4810
+ seen.add(key);
4811
+ unique.push(h);
4812
+ if (unique.length >= 4) break;
4813
+ }
4814
+ if (unique.length > 0) {
4815
+ process.stdout.write('\n---\nOther recent handoffs:\n');
4816
+ for (const h of unique) {
4817
+ const shortId = (h.session_id || '').slice(0, 8);
4818
+ process.stdout.write(` ${shortId} [${h.status || '?'}] ${h.goal || ''}\n`);
4819
+ }
4820
+ process.stdout.write(`(Pass any short id as: npx cf-memory-mcp resume <id>)\n`);
4740
4821
  }
4741
- process.stdout.write(`(Pass any short id as: npx cf-memory-mcp resume <id>)\n`);
4742
4822
  }
4743
4823
  process.exit(0);
4744
4824
  } else {
@@ -5280,14 +5360,15 @@ async function runDeleteCli() {
5280
5360
  }
5281
5361
  const { positional, flags } = parseCliArgs(process.argv.slice(3));
5282
5362
  const idArg = positional[0];
5283
- const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path);
5363
+ const bulkMode = !idArg && (flags.older_than_days !== undefined || flags.status || flags.repo_path || flags.repo_path_prefix);
5284
5364
  if (!idArg && !bulkMode) {
5285
5365
  console.error('Usage:');
5286
5366
  console.error(' cf-memory-mcp delete <session-id-or-prefix> # single delete');
5287
5367
  console.error(' cf-memory-mcp delete --older-than 30d # bulk delete by age');
5288
5368
  console.error(' cf-memory-mcp delete --status abandoned # bulk by status');
5289
- console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by repo');
5290
- console.error(' Combine filters; use --yes to confirm >5 deletions.');
5369
+ console.error(' cf-memory-mcp delete --repo /path/to/repo # bulk by exact repo');
5370
+ console.error(' cf-memory-mcp delete --repo-prefix /tmp/cfm- # bulk by repo prefix');
5371
+ console.error(' Combine filters; without --yes you get a dry-run preview.');
5291
5372
  process.exit(1);
5292
5373
  }
5293
5374
  const server = new CFMemoryMCP();
@@ -5298,6 +5379,7 @@ async function runDeleteCli() {
5298
5379
  if (flags.status) args.status = flags.status;
5299
5380
  if (flags.older_than_days !== undefined) args.older_than_days = flags.older_than_days;
5300
5381
  if (flags.repo_path) args.repo_path = flags.repo_path;
5382
+ if (flags.repo_path_prefix) args.repo_path_prefix = flags.repo_path_prefix;
5301
5383
 
5302
5384
  // Dry-run preview when --yes is missing. The server returns
5303
5385
  // {deleted:false, deleted_count, deleted_session_ids[]} so the
@@ -5308,6 +5390,16 @@ async function runDeleteCli() {
5308
5390
  method: 'tools/call',
5309
5391
  params: { name: 'delete_session', arguments: { ...args, dry_run: true } },
5310
5392
  });
5393
+ // Surface JSON-RPC errors (e.g. "repo_path_prefix too short")
5394
+ // instead of swallowing them into a "no matches" message.
5395
+ if (previewRes?.error) {
5396
+ if (flags.json) {
5397
+ process.stdout.write(JSON.stringify({ error: previewRes.error }, null, 2) + '\n');
5398
+ } else {
5399
+ process.stderr.write(`Error: ${previewRes.error.message || JSON.stringify(previewRes.error)}\n`);
5400
+ }
5401
+ process.exit(1);
5402
+ }
5311
5403
  const previewText = previewRes?.result?.content?.[0]?.text;
5312
5404
  const preview = JSON.parse(previewText || '{}');
5313
5405
  if (flags.json) {
@@ -5364,6 +5456,14 @@ async function runDeleteCli() {
5364
5456
  method: 'tools/call',
5365
5457
  params: { name: 'delete_session', arguments: args },
5366
5458
  });
5459
+ if (deleteRes?.error) {
5460
+ if (flags.json) {
5461
+ process.stdout.write(JSON.stringify({ error: deleteRes.error }, null, 2) + '\n');
5462
+ } else {
5463
+ process.stderr.write(`Error: ${deleteRes.error.message || JSON.stringify(deleteRes.error)}\n`);
5464
+ }
5465
+ process.exit(1);
5466
+ }
5367
5467
  const deleteText = deleteRes?.result?.content?.[0]?.text;
5368
5468
  const payload = JSON.parse(deleteText || '{}');
5369
5469
  // Implicit-cache invalidation: if any deleted id matches the
@@ -5409,6 +5509,14 @@ async function runDeleteCli() {
5409
5509
  method: 'tools/call',
5410
5510
  params: { name: 'delete_session', arguments: { session_id: fullId } },
5411
5511
  });
5512
+ if (deleteRes?.error) {
5513
+ if (flags.json) {
5514
+ process.stdout.write(JSON.stringify({ error: deleteRes.error, session_id: fullId }, null, 2) + '\n');
5515
+ } else {
5516
+ process.stderr.write(`Error: ${deleteRes.error.message || JSON.stringify(deleteRes.error)}\n`);
5517
+ }
5518
+ process.exit(1);
5519
+ }
5412
5520
  const deleteText = deleteRes?.result?.content?.[0]?.text;
5413
5521
  const payload = JSON.parse(deleteText || '{}');
5414
5522
  // Drop the implicit-session disk cache if the deleted id matches
@@ -5448,6 +5556,7 @@ function runEnvCli() {
5448
5556
  ['CF_MEMORY_SHUTDOWN_HANDOFF', 'default: on; "off" disables the SIGINT/SIGTERM handoff flush'],
5449
5557
  ['CF_MEMORY_PREWARM_RESUME', 'default: on; "off" disables the background resume prewarm at bridge startup'],
5450
5558
  ['CF_MEMORY_RESUME_DIFF', 'default: on; "off" disables git-diff injection into resume responses'],
5559
+ ['CF_MEMORY_BRANCH_STATE', 'default: on; "off" disables the "Branch state (since handoff was written)" block in `cfm resume`'],
5451
5560
  ['CF_MEMORY_DISK_CACHE', 'default: on; "off" disables ~/.cf-memory/handoff-*.json disk caching'],
5452
5561
  ['CF_MEMORY_NO_RETRY', 'default: off; "1" disables the single retry-on-transient-failure'],
5453
5562
  ['CF_MEMORY_LOG', 'default: off; "1" writes DEBUG/ERROR lines to ~/.cf-memory/bridge.log'],
@@ -5766,12 +5875,15 @@ const PER_COMMAND_HELP = {
5766
5875
  --uninstall Remove a previously-installed script.
5767
5876
  --all-shells With --install: install for every shell at once.`,
5768
5877
  delete: `cf-memory-mcp delete <session-id-or-prefix> [--json]
5769
- cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] --yes [--json]
5878
+ cf-memory-mcp delete [--status STATUS] [--older-than 30d] [--repo PATH] [--repo-prefix PFX] --yes [--json]
5770
5879
  Delete session row(s) and any session_summary memories tied to them.
5771
5880
  <session-id> Single delete: full UUID or short prefix (>=8 chars).
5772
5881
  --status STATUS Bulk filter by status (abandoned, completed, ...).
5773
5882
  --older-than 30d Bulk filter by age (suffixes: d, h, m).
5774
- --repo PATH Bulk filter by repo_path.
5883
+ --repo PATH Bulk filter by repo_path (exact match).
5884
+ --repo-prefix PFX Bulk filter by repo_path PREFIX (min 4 chars).
5885
+ E.g. --repo-prefix /tmp/cfm-lifecycle- nukes
5886
+ accumulated benchmark artifacts in one call.
5775
5887
  --yes, -y Required for bulk delete to confirm intent.
5776
5888
  --json, -j Emit JSON.
5777
5889
  Exit codes: 0 = deleted, 2 = bulk missing --yes, 3 = nothing matched.`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.63.0",
3
+ "version": "3.65.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": {