cf-memory-mcp 3.50.0 → 3.51.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.
@@ -3919,7 +3919,7 @@ if (process.argv.includes('--version') || process.argv.includes('-v')) {
3919
3919
 
3920
3920
  // Global --help only when no subcommand is present. With a subcommand, fall
3921
3921
  // through to the per-command help dispatch below.
3922
- const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history', 'link']);
3922
+ const SUBCOMMANDS = new Set(['resume', 'list', 'checkpoint', 'status', 'clean', 'export', 'import', 'doctor', 'completion', 'delete', 'env', 'history', 'link', 'explain']);
3923
3923
  const hasSubcommand = process.argv[2] && SUBCOMMANDS.has(process.argv[2]);
3924
3924
 
3925
3925
  if (!hasSubcommand && (process.argv.includes('--help') || process.argv.includes('-h'))) {
@@ -3935,6 +3935,7 @@ Usage:
3935
3935
  npx cf-memory-mcp history List all handoffs for cwd, chronological
3936
3936
  npx cf-memory-mcp checkpoint ["<goal>"] Snapshot current state (keep_open)
3937
3937
  npx cf-memory-mcp link <child> --parent <id> Retroactively link a child session to a parent
3938
+ npx cf-memory-mcp explain <id> Break down the handoff's quality_score
3938
3939
  npx cf-memory-mcp status Show bridge state + server resume availability
3939
3940
  npx cf-memory-mcp clean Delete local disk cache for current cwd
3940
3941
  npx cf-memory-mcp clean --all Delete ALL local disk caches
@@ -4576,6 +4577,102 @@ async function runResumeCli() {
4576
4577
  }
4577
4578
  }
4578
4579
 
4580
+ async function runExplainCli() {
4581
+ if (!API_KEY) {
4582
+ console.error('Error: CF_MEMORY_API_KEY environment variable is required');
4583
+ process.exit(1);
4584
+ }
4585
+ const { positional, flags } = parseCliArgs(process.argv.slice(3));
4586
+ const idArg = positional[0];
4587
+ if (!idArg) {
4588
+ console.error('Usage: cf-memory-mcp explain <session-id-or-prefix>');
4589
+ process.exit(1);
4590
+ }
4591
+ const server = new CFMemoryMCP();
4592
+ server.logDebug = () => {};
4593
+ try {
4594
+ const response = await server.makeRequest({
4595
+ jsonrpc: '2.0', id: `cli-explain-${Date.now()}`,
4596
+ method: 'tools/call',
4597
+ params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: idArg } },
4598
+ });
4599
+ const text = response?.result?.content?.[0]?.text;
4600
+ const payload = JSON.parse(text || '{}');
4601
+ const envelope = payload.resume_handoff;
4602
+ if (!envelope?.handoff) {
4603
+ process.stderr.write((payload.empty_hint || `No handoff found for "${idArg}".`) + '\n');
4604
+ process.exit(3);
4605
+ }
4606
+ const h = envelope.handoff;
4607
+ const ageMin = envelope.handoff_age_minutes;
4608
+ const sizeBytes = envelope.handoff_size_bytes ?? 0;
4609
+
4610
+ // Mirror the server-side rubric (see computeQualityScore).
4611
+ const components = [];
4612
+ const add = (label, points, present, detail) =>
4613
+ components.push({ label, points, present, detail });
4614
+
4615
+ add('goal present (>=5 chars)', 0.20, !!(h.goal && h.goal.length >= 5), h.goal ? `"${h.goal.slice(0,40)}"` : 'missing');
4616
+ add('next_steps present', 0.20,
4617
+ Array.isArray(h.next_steps) && h.next_steps.length > 0,
4618
+ `${h.next_steps?.length ?? 0} step${(h.next_steps?.length ?? 0) === 1 ? '' : 's'}`);
4619
+ add('code_anchors present', 0.15,
4620
+ Array.isArray(h.code_anchors) && h.code_anchors.length > 0,
4621
+ `${h.code_anchors?.length ?? 0} anchor${(h.code_anchors?.length ?? 0) === 1 ? '' : 's'}`);
4622
+ add('files_touched present', 0.10,
4623
+ Array.isArray(h.files_touched) && h.files_touched.length > 0,
4624
+ `${h.files_touched?.length ?? 0} file${(h.files_touched?.length ?? 0) === 1 ? '' : 's'}`);
4625
+ add('decisions present', 0.10,
4626
+ Array.isArray(h.decisions) && h.decisions.length > 0,
4627
+ `${h.decisions?.length ?? 0} decision${(h.decisions?.length ?? 0) === 1 ? '' : 's'}`);
4628
+
4629
+ // Recency
4630
+ let recency = 0;
4631
+ let recencyDetail = '?';
4632
+ if (ageMin !== undefined) {
4633
+ if (ageMin <= 1440) recency = 0.15;
4634
+ else if (ageMin <= 10080) recency = 0.15 * (1 - (ageMin - 1440) / (10080 - 1440));
4635
+ recencyDetail = `${ageMin}m old`;
4636
+ }
4637
+ components.push({ label: 'recency (decays past 24h)', points: 0.15, present: recency > 0, detail: `${recencyDetail} → +${recency.toFixed(3)}` });
4638
+
4639
+ // Size in target band
4640
+ let sizeBonus = 0;
4641
+ let sizeDetail;
4642
+ if (sizeBytes >= 500 && sizeBytes <= 8000) { sizeBonus = 0.10; sizeDetail = 'in target 500-8000 byte range'; }
4643
+ else if (sizeBytes >= 200 && sizeBytes <= 16000) { sizeBonus = 0.05; sizeDetail = 'in extended 200-16000 byte range'; }
4644
+ else { sizeDetail = sizeBytes < 200 ? 'too small (<200 bytes)' : 'too large (>16000 bytes)'; }
4645
+ components.push({ label: 'size sweet spot', points: 0.10, present: sizeBonus > 0, detail: `${sizeBytes} bytes — ${sizeDetail}` });
4646
+
4647
+ if (flags.json) {
4648
+ process.stdout.write(JSON.stringify({
4649
+ session_id: envelope.session_id,
4650
+ quality_score: envelope.quality_score,
4651
+ components,
4652
+ }, null, 2) + '\n');
4653
+ process.exit(0);
4654
+ }
4655
+ const shortId = (envelope.session_id || '').slice(0, 8);
4656
+ process.stdout.write(`Quality breakdown for ${shortId} (final score: ${envelope.quality_score}):\n\n`);
4657
+ let earned = 0;
4658
+ for (const c of components) {
4659
+ const mark = c.present ? '✓' : '✗';
4660
+ const earned_pts = c.present ? c.points : 0;
4661
+ earned += earned_pts;
4662
+ process.stdout.write(` ${mark} ${c.label.padEnd(32)} +${c.points.toFixed(2)} ${c.detail || ''}\n`);
4663
+ }
4664
+ process.stdout.write(`\n Total earned: ${earned.toFixed(2)} / 1.00\n`);
4665
+ const missing = components.filter(c => !c.present);
4666
+ if (missing.length > 0) {
4667
+ process.stdout.write(`\n To improve: add ${missing.map(m => m.label).join(', ')}\n`);
4668
+ }
4669
+ process.exit(0);
4670
+ } catch (err) {
4671
+ console.error('explain command failed:', err.message);
4672
+ process.exit(1);
4673
+ }
4674
+ }
4675
+
4579
4676
  async function runListCli() {
4580
4677
  if (!API_KEY) {
4581
4678
  console.error('Error: CF_MEMORY_API_KEY environment variable is required');
@@ -5019,7 +5116,8 @@ function runEnvCli() {
5019
5116
  function runCompletionCli() {
5020
5117
  const shell = process.argv[3] || 'bash';
5021
5118
  const install = process.argv.includes('--install');
5022
- const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
5119
+ const uninstall = process.argv.includes('--uninstall');
5120
+ const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'explain', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
5023
5121
  const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
5024
5122
 
5025
5123
  // Determine the install target for each shell. Use user-local paths
@@ -5047,6 +5145,27 @@ function runCompletionCli() {
5047
5145
  }
5048
5146
  return null;
5049
5147
  })();
5148
+ // --uninstall: delete the previously-installed completion file. No
5149
+ // need to generate the script. Idempotent.
5150
+ if (uninstall) {
5151
+ if (!installTarget) {
5152
+ process.stderr.write(`No uninstall target for shell ${shell}.\n`);
5153
+ process.exit(1);
5154
+ }
5155
+ try {
5156
+ if (fs.existsSync(installTarget)) {
5157
+ fs.unlinkSync(installTarget);
5158
+ process.stderr.write(`Uninstalled ${shell} completion from ${installTarget}\n`);
5159
+ } else {
5160
+ process.stderr.write(`Nothing to uninstall (no file at ${installTarget}).\n`);
5161
+ }
5162
+ process.exit(0);
5163
+ } catch (err) {
5164
+ process.stderr.write(`Failed to uninstall: ${err.message}\n`);
5165
+ process.exit(1);
5166
+ }
5167
+ }
5168
+
5050
5169
  // Buffer the output so we can either print or write-to-disk.
5051
5170
  let output = '';
5052
5171
  const emit = (chunk) => { output += chunk; };
@@ -5260,6 +5379,11 @@ const PER_COMMAND_HELP = {
5260
5379
  a parent in the chain. Useful when you forgot --force on checkpoint
5261
5380
  or want to manually chain sessions across repos.
5262
5381
  --json, -j Emit a JSON status object.`,
5382
+ explain: `cf-memory-mcp explain <session-id-or-prefix> [--json]
5383
+ Break down the handoff's quality_score into its rubric components,
5384
+ showing what's earned + what's missing. Lets users see why a handoff
5385
+ scored 0.5 vs 0.9 — and how to improve it.
5386
+ --json, -j Emit the breakdown as JSON.`,
5263
5387
  };
5264
5388
 
5265
5389
  function printPerCommandHelp(cmd) {
@@ -6045,6 +6169,11 @@ if (process.argv[2] === 'link') {
6045
6169
  return;
6046
6170
  }
6047
6171
 
6172
+ if (process.argv[2] === 'explain') {
6173
+ runExplainCli();
6174
+ return;
6175
+ }
6176
+
6048
6177
  if (process.argv.includes('--diagnose')) {
6049
6178
  (async () => {
6050
6179
  console.log(`CF Memory MCP v${PACKAGE_VERSION} - Diagnostics`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cf-memory-mcp",
3
- "version": "3.50.0",
3
+ "version": "3.51.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": {