@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.
@@ -1,6 +1,7 @@
1
1
  import { createHmac } from 'node:crypto';
2
2
  import type { Redis } from 'ioredis';
3
- import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge } from '../ckg.js';
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:"%H|||%s|||%ad|||%an" --date=short --no-merges -n ${maxCommits}`;
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
- const commits = logOutput.trim().split('\n').filter(Boolean).map(line => {
938
- const [sha, subject, date, author] = line.split('|||');
939
- return { sha: (sha ?? '').trim(), subject: (subject ?? '').trim(), date: (date ?? '').trim(), author: (author ?? '').trim() };
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
- file_paths: [], commands: [`git show ${commit.sha.slice(0, 8)}`],
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.60';
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 · 98 MCP tools · GDPR · EU servers',
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 · 98 MCP tools\x1b[0m');
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: