@cachly-dev/mcp-server 0.10.60 → 0.10.62
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/CHANGELOG.md +23 -0
- package/README.md +1 -1
- package/dist/src/__tests__/brain-flow.test.js +98 -0
- package/dist/src/__tests__/brain-flow.test.js.map +1 -1
- package/dist/src/handlers/brain.js +203 -5
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/handlers/fedbrain.js +37 -10
- package/dist/src/handlers/fedbrain.js.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/tools.d.ts +34 -0
- package/dist/src/tools.js +32 -0
- package/dist/src/tools.js.map +1 -1
- package/package.json +2 -2
- package/server.json +3 -3
- package/src/__tests__/brain-flow.test.ts +116 -0
- package/src/handlers/brain.ts +222 -4
- package/src/handlers/fedbrain.ts +38 -10
- package/src/index.ts +3 -3
- package/src/tools.ts +34 -0
package/src/handlers/fedbrain.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHmac } from 'node:crypto';
|
|
2
2
|
import type { Redis } from 'ioredis';
|
|
3
|
-
import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge
|
|
3
|
+
import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge,
|
|
4
|
+
ckgUpsertPersonNode, ckgUpsertFileNode } from '../ckg.js';
|
|
4
5
|
import type { CKGEdge, CKGNode } from '../ckg.js';
|
|
5
6
|
import { safeJsonParse } from '../utils.js';
|
|
6
7
|
|
|
@@ -920,12 +921,11 @@ export async function handleFedbrainTool(
|
|
|
920
921
|
isIncremental = Boolean(lastSha);
|
|
921
922
|
}
|
|
922
923
|
|
|
923
|
-
// Build git log command
|
|
924
|
-
// In incremental mode, only fetch commits after the last processed SHA
|
|
924
|
+
// Build git log command — use --name-only to get changed files per commit
|
|
925
925
|
const sinceFlag = since ? `--since="${since}"` : '';
|
|
926
926
|
const afterFlag = isIncremental ? `${lastSha}..HEAD` : '';
|
|
927
927
|
const revRange = afterFlag || branch;
|
|
928
|
-
const logCmd = `git log ${revRange} ${sinceFlag} --pretty=format:"
|
|
928
|
+
const logCmd = `git log ${revRange} ${sinceFlag} --pretty=format:"COMMIT|||%H|||%s|||%ad|||%an" --date=short --no-merges --name-only -n ${maxCommits}`;
|
|
929
929
|
|
|
930
930
|
let logOutput = '';
|
|
931
931
|
try {
|
|
@@ -934,10 +934,21 @@ export async function handleFedbrainTool(
|
|
|
934
934
|
return `❌ git log failed: ${(e as Error).message}. Check \`repo_path\` and \`branch\`.`;
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
937
|
+
// Parse multi-line output: each commit block = header line + blank line + file lines
|
|
938
|
+
type CommitEntry = { sha: string; subject: string; date: string; author: string; files: string[] };
|
|
939
|
+
const commits: CommitEntry[] = [];
|
|
940
|
+
let current: CommitEntry | null = null;
|
|
941
|
+
for (const raw of logOutput.split('\n')) {
|
|
942
|
+
const line = raw.trim();
|
|
943
|
+
if (line.startsWith('COMMIT|||')) {
|
|
944
|
+
if (current) commits.push(current);
|
|
945
|
+
const [, sha, subject, date, author] = line.split('|||');
|
|
946
|
+
current = { sha: (sha ?? '').trim(), subject: (subject ?? '').trim(), date: (date ?? '').trim(), author: (author ?? '').trim(), files: [] };
|
|
947
|
+
} else if (line && current) {
|
|
948
|
+
current.files.push(line);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (current) commits.push(current);
|
|
941
952
|
|
|
942
953
|
if (commits.length === 0) {
|
|
943
954
|
if (isIncremental) {
|
|
@@ -994,13 +1005,17 @@ export async function handleFedbrainTool(
|
|
|
994
1005
|
const topic = `${category}:${domain}`;
|
|
995
1006
|
categoryCount.set(category, (categoryCount.get(category) ?? 0) + 1);
|
|
996
1007
|
|
|
1008
|
+
const commitFiles = commit.files.filter(f => f.length > 0).slice(0, 12);
|
|
997
1009
|
const lessonObj = {
|
|
998
1010
|
topic, outcome, severity,
|
|
999
1011
|
what_worked: commit.subject.slice(0, 200),
|
|
1000
1012
|
what_failed: '',
|
|
1001
1013
|
context: `git:${commit.sha.slice(0, 8)} by ${commit.author} on ${commit.date}`,
|
|
1002
|
-
|
|
1014
|
+
author: commit.author || undefined,
|
|
1015
|
+
file_paths: commitFiles,
|
|
1016
|
+
commands: [`git show ${commit.sha.slice(0, 8)}`],
|
|
1003
1017
|
tags: ['brain_from_git', category, 'git-history'],
|
|
1018
|
+
visibility: 'team',
|
|
1004
1019
|
depends_on: [], recall_count: 0, ts, verified_at: ts,
|
|
1005
1020
|
confidence: 0.55, // lower confidence for auto-inferred lessons
|
|
1006
1021
|
audit_trail: [{ ts, action: 'brain_from_git', sha: commit.sha.slice(0, 8) }],
|
|
@@ -1021,9 +1036,22 @@ export async function handleFedbrainTool(
|
|
|
1021
1036
|
await redis.ltrim('cachly:lessons:brain_from_git:all', -500, -1);
|
|
1022
1037
|
await redis.expire('cachly:lessons:brain_from_git:all', 90 * 86400);
|
|
1023
1038
|
|
|
1024
|
-
// Update CKG
|
|
1039
|
+
// Update CKG — concept node + person node + file nodes
|
|
1025
1040
|
const conceptId = ckgSlug(topic);
|
|
1026
1041
|
await ckgUpsertNode(redis, conceptId, category, 'git-derived');
|
|
1042
|
+
|
|
1043
|
+
// Phase 3A: auto-build person + file nodes from git history
|
|
1044
|
+
if (commit.author) {
|
|
1045
|
+
try {
|
|
1046
|
+
const personId = await ckgUpsertPersonNode(redis, commit.author, category);
|
|
1047
|
+
await ckgUpdateEdge(redis, personId, 'authored', conceptId, outcome === 'success', outcome === 'partial');
|
|
1048
|
+
for (const fp of commitFiles.slice(0, 8)) {
|
|
1049
|
+
const fileId = await ckgUpsertFileNode(redis, fp);
|
|
1050
|
+
await ckgUpdateEdge(redis, personId, 'touched', fileId, true);
|
|
1051
|
+
}
|
|
1052
|
+
} catch { /* non-critical */ }
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1027
1055
|
ingested++;
|
|
1028
1056
|
}
|
|
1029
1057
|
|
package/src/index.ts
CHANGED
|
@@ -73,7 +73,7 @@ import { Redis } from 'ioredis';
|
|
|
73
73
|
const API_URL = process.env.CACHLY_API_URL ?? 'https://api.cachly.dev';
|
|
74
74
|
let JWT = process.env.CACHLY_JWT ?? '';
|
|
75
75
|
const _EMBED_MODEL = process.env.CACHLY_EMBED_MODEL ?? '';
|
|
76
|
-
const CURRENT_VERSION = '0.10.
|
|
76
|
+
const CURRENT_VERSION = '0.10.62';
|
|
77
77
|
|
|
78
78
|
// Max time to wait for a freshly-provisioned instance to become "running" before
|
|
79
79
|
// giving up. Free-tier provisioning in high-latency regions can take 45–90s, so the
|
|
@@ -572,7 +572,7 @@ async function handleTool(name: string, args: Record<string, unknown>): Promise<
|
|
|
572
572
|
'',
|
|
573
573
|
'After sign-in: call **any tool again** — your Brain activates instantly.',
|
|
574
574
|
'',
|
|
575
|
-
'✨ Free forever · No credit card ·
|
|
575
|
+
'✨ Free forever · No credit card · 100 MCP tools · GDPR · EU servers',
|
|
576
576
|
].join('\n');
|
|
577
577
|
}
|
|
578
578
|
|
|
@@ -1816,7 +1816,7 @@ if (!process.argv[2] && process.stdout.isTTY) {
|
|
|
1816
1816
|
console.log(' \x1b[36m npx @cachly-dev/mcp-server@latest upgrade\x1b[0m ← Check for updates');
|
|
1817
1817
|
console.log('');
|
|
1818
1818
|
console.log(' \x1b[90mWorks with: Claude Code · Cursor · Windsurf · GitHub Copilot · Cline · Zed\x1b[0m');
|
|
1819
|
-
console.log(' \x1b[90mFree forever · GDPR · German servers ·
|
|
1819
|
+
console.log(' \x1b[90mFree forever · GDPR · German servers · 100 MCP tools\x1b[0m');
|
|
1820
1820
|
console.log('');
|
|
1821
1821
|
process.exit(0);
|
|
1822
1822
|
}
|
package/src/tools.ts
CHANGED
|
@@ -859,6 +859,40 @@ const TOOLS = [
|
|
|
859
859
|
required: ['instance_id'],
|
|
860
860
|
},
|
|
861
861
|
},
|
|
862
|
+
{
|
|
863
|
+
name: 'skill_gaps',
|
|
864
|
+
description:
|
|
865
|
+
'Show knowledge blind spots in your Brain — domains with unresolved failures, ' +
|
|
866
|
+
'lessons with missing attribution, and areas where brain_who_knows cannot help. ' +
|
|
867
|
+
'Run periodically to find where to focus knowledge capture effort. ' +
|
|
868
|
+
'Returns a prioritized list: 🔴 critical (failures with no solutions) → 🟡 warn → 🔵 info. ' +
|
|
869
|
+
'Pairs with brain_coverage for a full knowledge-health picture.',
|
|
870
|
+
inputSchema: {
|
|
871
|
+
type: 'object',
|
|
872
|
+
properties: {
|
|
873
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
874
|
+
min_failures: { type: 'number', description: 'Min failure count to flag a domain as a gap (default: 1)' },
|
|
875
|
+
},
|
|
876
|
+
required: ['instance_id'],
|
|
877
|
+
},
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
name: 'brain_coverage',
|
|
881
|
+
description:
|
|
882
|
+
'Knowledge-coverage health score for your codebase — scored 0-100. ' +
|
|
883
|
+
'Reports: total lessons, success ratio, attribution completeness, team engagement, and file coverage vs git ls-files. ' +
|
|
884
|
+
'Run after brain_from_git or periodically to track knowledge-capture progress. ' +
|
|
885
|
+
'Use skill_gaps to find what to fix. ' +
|
|
886
|
+
'Example: brain_coverage() → "🟢 Overall score: 78/100 · 42 lessons · 6 contributors · 31% files covered".',
|
|
887
|
+
inputSchema: {
|
|
888
|
+
type: 'object',
|
|
889
|
+
properties: {
|
|
890
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
891
|
+
repo_path: { type: 'string', description: 'Path to the git repository (default: current directory)' },
|
|
892
|
+
},
|
|
893
|
+
required: ['instance_id'],
|
|
894
|
+
},
|
|
895
|
+
},
|
|
862
896
|
{
|
|
863
897
|
name: 'sync_file_changes',
|
|
864
898
|
description:
|