cf-memory-mcp 3.49.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.
- package/bin/cf-memory-mcp.js +181 -5
- package/package.json +1 -1
package/bin/cf-memory-mcp.js
CHANGED
|
@@ -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
|
|
@@ -4399,21 +4400,34 @@ async function runResumeCli() {
|
|
|
4399
4400
|
// --diff <other-id>: compare two handoffs. Shows added/removed
|
|
4400
4401
|
// next_steps, branch shift, status change, files diff. Useful
|
|
4401
4402
|
// for "what changed between yesterday's session and today's?".
|
|
4403
|
+
// Special value "--parent" diffs against the chain parent.
|
|
4402
4404
|
if (flags.diff) {
|
|
4403
4405
|
if (!found) {
|
|
4404
4406
|
process.stderr.write('No source handoff available to diff against.\n');
|
|
4405
4407
|
process.exit(3);
|
|
4406
4408
|
}
|
|
4409
|
+
let diffTarget = flags.diff;
|
|
4410
|
+
// --diff --parent: resolve to the parent_session_id from the
|
|
4411
|
+
// current handoff so users can do "what's new since the
|
|
4412
|
+
// last checkpoint" without typing the parent's id.
|
|
4413
|
+
if (diffTarget === '--parent' || diffTarget === 'parent') {
|
|
4414
|
+
const parentId = payload.resume_handoff.handoff?.parent_session_id;
|
|
4415
|
+
if (!parentId) {
|
|
4416
|
+
process.stderr.write('This handoff has no parent_session_id to diff against.\n');
|
|
4417
|
+
process.exit(3);
|
|
4418
|
+
}
|
|
4419
|
+
diffTarget = parentId;
|
|
4420
|
+
}
|
|
4407
4421
|
const otherRes = await server.makeRequest({
|
|
4408
4422
|
jsonrpc: '2.0', id: `cli-diff-${Date.now()}`,
|
|
4409
4423
|
method: 'tools/call',
|
|
4410
|
-
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint:
|
|
4424
|
+
params: { name: 'get_context_bootstrap', arguments: { resume: true, session_id_hint: diffTarget } },
|
|
4411
4425
|
});
|
|
4412
4426
|
const otherText = otherRes?.result?.content?.[0]?.text;
|
|
4413
4427
|
const otherPayload = JSON.parse(otherText || '{}');
|
|
4414
4428
|
const otherHandoff = otherPayload.resume_handoff?.handoff;
|
|
4415
4429
|
if (!otherHandoff) {
|
|
4416
|
-
process.stderr.write(`Could not fetch handoff "${
|
|
4430
|
+
process.stderr.write(`Could not fetch handoff "${diffTarget}" to diff against.\n`);
|
|
4417
4431
|
process.exit(3);
|
|
4418
4432
|
}
|
|
4419
4433
|
const h = payload.resume_handoff.handoff;
|
|
@@ -4563,6 +4577,102 @@ async function runResumeCli() {
|
|
|
4563
4577
|
}
|
|
4564
4578
|
}
|
|
4565
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
|
+
|
|
4566
4676
|
async function runListCli() {
|
|
4567
4677
|
if (!API_KEY) {
|
|
4568
4678
|
console.error('Error: CF_MEMORY_API_KEY environment variable is required');
|
|
@@ -5006,7 +5116,8 @@ function runEnvCli() {
|
|
|
5006
5116
|
function runCompletionCli() {
|
|
5007
5117
|
const shell = process.argv[3] || 'bash';
|
|
5008
5118
|
const install = process.argv.includes('--install');
|
|
5009
|
-
const
|
|
5119
|
+
const uninstall = process.argv.includes('--uninstall');
|
|
5120
|
+
const commands = ['resume', 'list', 'history', 'checkpoint', 'link', 'explain', 'status', 'clean', 'export', 'import', 'delete', 'doctor', 'env', 'completion'];
|
|
5010
5121
|
const flags = ['--json', '-j', '--limit', '-n', '--md', '--all', '--force', '-f', '--version', '-v', '--help', '-h', '--diagnose'];
|
|
5011
5122
|
|
|
5012
5123
|
// Determine the install target for each shell. Use user-local paths
|
|
@@ -5034,6 +5145,27 @@ function runCompletionCli() {
|
|
|
5034
5145
|
}
|
|
5035
5146
|
return null;
|
|
5036
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
|
+
|
|
5037
5169
|
// Buffer the output so we can either print or write-to-disk.
|
|
5038
5170
|
let output = '';
|
|
5039
5171
|
const emit = (chunk) => { output += chunk; };
|
|
@@ -5247,6 +5379,11 @@ const PER_COMMAND_HELP = {
|
|
|
5247
5379
|
a parent in the chain. Useful when you forgot --force on checkpoint
|
|
5248
5380
|
or want to manually chain sessions across repos.
|
|
5249
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.`,
|
|
5250
5387
|
};
|
|
5251
5388
|
|
|
5252
5389
|
function printPerCommandHelp(cmd) {
|
|
@@ -5359,8 +5496,25 @@ async function runDoctorCli() {
|
|
|
5359
5496
|
}
|
|
5360
5497
|
}
|
|
5361
5498
|
|
|
5499
|
+
// Optional: handoff stats summary at the bottom (visibility into
|
|
5500
|
+
// how much resume context is captured for the current user).
|
|
5501
|
+
let handoffStats = null;
|
|
5502
|
+
if (API_KEY) {
|
|
5503
|
+
try {
|
|
5504
|
+
const statsRes = await server.makeRequestOnce({
|
|
5505
|
+
jsonrpc: '2.0', id: `doctor-stats-${Date.now()}`,
|
|
5506
|
+
method: 'tools/call', params: { name: 'get_stats', arguments: {} },
|
|
5507
|
+
});
|
|
5508
|
+
const statsText = statsRes?.result?.content?.[0]?.text;
|
|
5509
|
+
if (statsText) {
|
|
5510
|
+
const statsPayload = JSON.parse(statsText);
|
|
5511
|
+
if (statsPayload?.handoffs) handoffStats = statsPayload.handoffs;
|
|
5512
|
+
}
|
|
5513
|
+
} catch (_) { /* skip on failure */ }
|
|
5514
|
+
}
|
|
5515
|
+
|
|
5362
5516
|
if (flags.json) {
|
|
5363
|
-
process.stdout.write(JSON.stringify({ checks }, null, 2) + '\n');
|
|
5517
|
+
process.stdout.write(JSON.stringify({ checks, ...(handoffStats ? { handoffs: handoffStats } : {}) }, null, 2) + '\n');
|
|
5364
5518
|
process.exit(checks.every(c => c.ok) ? 0 : 1);
|
|
5365
5519
|
}
|
|
5366
5520
|
process.stdout.write(`cf-memory-mcp v${PACKAGE_VERSION} — doctor\n\n`);
|
|
@@ -5373,6 +5527,23 @@ async function runDoctorCli() {
|
|
|
5373
5527
|
process.stdout.write(` → ${c.fix}\n`);
|
|
5374
5528
|
}
|
|
5375
5529
|
}
|
|
5530
|
+
if (handoffStats) {
|
|
5531
|
+
process.stdout.write('\nHandoff stats:\n');
|
|
5532
|
+
process.stdout.write(` total: ${handoffStats.total}\n`);
|
|
5533
|
+
if (handoffStats.by_status) {
|
|
5534
|
+
const byStatus = Object.entries(handoffStats.by_status)
|
|
5535
|
+
.map(([s, n]) => `${s}=${n}`).join(', ');
|
|
5536
|
+
process.stdout.write(` by_status: ${byStatus}\n`);
|
|
5537
|
+
}
|
|
5538
|
+
if (handoffStats.oldest_in_progress) {
|
|
5539
|
+
const o = handoffStats.oldest_in_progress;
|
|
5540
|
+
const shortId = (o.session_id || '').slice(0, 8);
|
|
5541
|
+
process.stdout.write(` oldest in_progress: ${shortId} (${o.age_minutes}m ago)\n`);
|
|
5542
|
+
}
|
|
5543
|
+
if (handoffStats.by_repo && handoffStats.by_repo.length > 0) {
|
|
5544
|
+
process.stdout.write(` top repos: ${handoffStats.by_repo.slice(0, 3).map(r => `${r.repo_path.split('/').pop()}=${r.count}`).join(', ')}\n`);
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5376
5547
|
process.stdout.write('\n');
|
|
5377
5548
|
process.stdout.write(anyFailed ? 'Some checks failed. See suggestions above.\n' : 'All checks passed.\n');
|
|
5378
5549
|
process.exit(anyFailed ? 1 : 0);
|
|
@@ -5998,6 +6169,11 @@ if (process.argv[2] === 'link') {
|
|
|
5998
6169
|
return;
|
|
5999
6170
|
}
|
|
6000
6171
|
|
|
6172
|
+
if (process.argv[2] === 'explain') {
|
|
6173
|
+
runExplainCli();
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
|
|
6001
6177
|
if (process.argv.includes('--diagnose')) {
|
|
6002
6178
|
(async () => {
|
|
6003
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.
|
|
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": {
|