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.
- package/bin/cf-memory-mcp.js +122 -10
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
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('
|
|
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.
|
|
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": {
|