@cachly-dev/mcp-server 0.10.59 → 0.10.60
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 +20 -0
- package/README.md +1 -1
- package/dist/src/__tests__/brain-flow.test.js +106 -9
- package/dist/src/__tests__/brain-flow.test.js.map +1 -1
- package/dist/src/handlers/brain.js +196 -7
- package/dist/src/handlers/brain.js.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/tools.d.ts +43 -1
- package/dist/src/tools.js +41 -1
- 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 +123 -9
- package/src/handlers/brain.ts +219 -7
- package/src/index.ts +3 -3
- package/src/tools.ts +43 -1
package/src/handlers/brain.ts
CHANGED
|
@@ -12,14 +12,21 @@ import { rerankByQuality } from '../rerank.js';
|
|
|
12
12
|
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
13
13
|
|
|
14
14
|
// ── Changelog (shown once per version in session_start) ──────────────────────
|
|
15
|
-
const MCP_VERSION = '0.10.
|
|
15
|
+
const MCP_VERSION = '0.10.60';
|
|
16
16
|
const WHATS_NEW: Record<string, string[]> = {
|
|
17
|
+
'0.10.60': [
|
|
18
|
+
`🆕 **Phase 3B: File map + team overview + visibility**`,
|
|
19
|
+
` 📁 \`brain_file_map(file_paths=[...])\` — experts + lessons per file before you touch it`,
|
|
20
|
+
` 🗺️ \`team_expertise_map()\` — full team skills matrix in one table`,
|
|
21
|
+
` 🔒 \`visibility: "private"\` on \`learn_from_attempts\` — private notes never leak to smart_recall`,
|
|
22
|
+
` 📊 98 MCP tools`,
|
|
23
|
+
],
|
|
17
24
|
'0.10.59': [
|
|
18
25
|
`🆕 **Phase 3A: Org-wide knowledge graph**`,
|
|
19
26
|
` 👥 \`brain_who_knows(topic="...")\` — find your team's experts on any topic instantly`,
|
|
20
27
|
` 🕸️ Person + File nodes auto-built from \`learn_from_attempts(author="...", file_paths=[...])\``,
|
|
21
28
|
` 👤 Author attribution now shown inline in \`smart_recall\` results`,
|
|
22
|
-
` 📊 96 MCP tools ·
|
|
29
|
+
` 📊 96 MCP tools · 386 tests green`,
|
|
23
30
|
],
|
|
24
31
|
'0.10.58': [
|
|
25
32
|
`🧹 Zero lint warnings — all unused imports cleaned across every handler`,
|
|
@@ -80,7 +87,7 @@ type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
|
|
|
80
87
|
export const BRAIN_TOOL_NAMES = new Set([
|
|
81
88
|
'learn_from_attempts', 'recall_best_solution', 'smart_recall',
|
|
82
89
|
'session_start', 'session_end', 'session_ping', 'session_handoff', 'auto_learn_session',
|
|
83
|
-
'brain_who_knows',
|
|
90
|
+
'brain_who_knows', 'brain_file_map', 'team_expertise_map',
|
|
84
91
|
]);
|
|
85
92
|
|
|
86
93
|
export async function handleBrainTool(
|
|
@@ -104,6 +111,7 @@ export async function handleBrainTool(
|
|
|
104
111
|
tags = [],
|
|
105
112
|
depends_on = [],
|
|
106
113
|
author = '',
|
|
114
|
+
visibility = 'team',
|
|
107
115
|
} = args as {
|
|
108
116
|
instance_id: string;
|
|
109
117
|
topic: string;
|
|
@@ -117,6 +125,7 @@ export async function handleBrainTool(
|
|
|
117
125
|
tags?: string[];
|
|
118
126
|
depends_on?: string[];
|
|
119
127
|
author?: string;
|
|
128
|
+
visibility?: 'public' | 'team' | 'private';
|
|
120
129
|
};
|
|
121
130
|
|
|
122
131
|
const redis = await getConnection(instance_id);
|
|
@@ -246,6 +255,7 @@ export async function handleBrainTool(
|
|
|
246
255
|
tags,
|
|
247
256
|
depends_on,
|
|
248
257
|
...(author ? { author } : {}),
|
|
258
|
+
visibility,
|
|
249
259
|
recall_count: recallCount,
|
|
250
260
|
ts,
|
|
251
261
|
verified_at: outcome === 'success' || outcome === 'partial' ? ts : undefined,
|
|
@@ -625,13 +635,13 @@ export async function handleBrainTool(
|
|
|
625
635
|
}
|
|
626
636
|
|
|
627
637
|
// ── Layer 2: Semantic search (parallel, optional) ────────────────────────
|
|
628
|
-
const inst = await apiFetch<Instance>(`/api/v1/instances/${instance_id}`);
|
|
638
|
+
const inst = await apiFetch<Instance | null>(`/api/v1/instances/${instance_id}`).catch(() => null);
|
|
629
639
|
type SemHit = { key: string; similarity: number; content: string };
|
|
630
640
|
const semHits: SemHit[] = [];
|
|
631
|
-
if (inst
|
|
641
|
+
if (inst?.vector_token && hasEmbedProvider()) {
|
|
632
642
|
try {
|
|
633
643
|
const embedding = await computeEmbedding(query);
|
|
634
|
-
const vectorUrl = `https://api.cachly.dev/v1/sem/${inst
|
|
644
|
+
const vectorUrl = `https://api.cachly.dev/v1/sem/${inst!.vector_token}`;
|
|
635
645
|
const searchRes = await fetch(`${vectorUrl}/search`, {
|
|
636
646
|
method: 'POST',
|
|
637
647
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -736,7 +746,14 @@ export async function handleBrainTool(
|
|
|
736
746
|
}
|
|
737
747
|
} catch { /* non-critical — keyword + semantic always available */ }
|
|
738
748
|
|
|
739
|
-
|
|
749
|
+
// Filter private lessons — they are only accessible via exact recall_best_solution
|
|
750
|
+
const hybridResults = [...hybridMap.values()]
|
|
751
|
+
.filter(r => {
|
|
752
|
+
if (!r.key.startsWith('cachly:lesson:best:')) return true;
|
|
753
|
+
const ld = safeJsonParse<{ visibility?: string }>(r.content, {});
|
|
754
|
+
return ld.visibility !== 'private';
|
|
755
|
+
})
|
|
756
|
+
.sort((a, b) => b.hybridScore - a.hybridScore);
|
|
740
757
|
|
|
741
758
|
// ── Build output ──────────────────────────────────────────────────────────
|
|
742
759
|
const lines: string[] = [`🧠 **Smart Recall** for: _"${query}"_\n`];
|
|
@@ -2123,6 +2140,201 @@ export async function handleBrainTool(
|
|
|
2123
2140
|
return lines.join('\n');
|
|
2124
2141
|
}
|
|
2125
2142
|
|
|
2143
|
+
// ── brain_file_map ────────────────────────────────────────────────────────
|
|
2144
|
+
// Phase 3B: "What do we know about these files?" — experts + lessons per file.
|
|
2145
|
+
case 'brain_file_map': {
|
|
2146
|
+
const {
|
|
2147
|
+
instance_id,
|
|
2148
|
+
file_paths = [],
|
|
2149
|
+
} = args as { instance_id: string; file_paths: string[] };
|
|
2150
|
+
|
|
2151
|
+
if (!file_paths.length) return '⚠️ Pass at least one file path.';
|
|
2152
|
+
|
|
2153
|
+
const redis = await getConnection(instance_id);
|
|
2154
|
+
const lines: string[] = [
|
|
2155
|
+
`## 📁 File Knowledge Map`,
|
|
2156
|
+
``,
|
|
2157
|
+
`_What cachly knows about the files you're about to touch:_`,
|
|
2158
|
+
``,
|
|
2159
|
+
];
|
|
2160
|
+
|
|
2161
|
+
for (const fp of file_paths.slice(0, 10)) {
|
|
2162
|
+
lines.push(`### \`${fp}\``);
|
|
2163
|
+
|
|
2164
|
+
// ── Experts via CKG file nodes ─────────────────────────────────────
|
|
2165
|
+
const fileId = `file:${ckgSlug(fp)}`;
|
|
2166
|
+
const inbound = await redis.smembers(`cachly:ckg:idx:to:${fileId}`);
|
|
2167
|
+
type ExpertEntry = { handle: string; touches: number; lastSeen: string };
|
|
2168
|
+
const experts: ExpertEntry[] = [];
|
|
2169
|
+
for (const ek of inbound) {
|
|
2170
|
+
const er = await redis.get(ek);
|
|
2171
|
+
if (!er) continue;
|
|
2172
|
+
const edge = safeJsonParse<CKGEdge | null>(er, null);
|
|
2173
|
+
if (!edge || edge.edgeType !== 'touched') continue;
|
|
2174
|
+
const personRaw = await redis.get(`cachly:ckg:node:${edge.from}`);
|
|
2175
|
+
if (!personRaw) continue;
|
|
2176
|
+
const pn = safeJsonParse<PersonNode | null>(personRaw, null);
|
|
2177
|
+
if (!pn || pn.type !== 'person') continue;
|
|
2178
|
+
experts.push({ handle: pn.handle, touches: edge.trials, lastSeen: edge.last_updated });
|
|
2179
|
+
}
|
|
2180
|
+
experts.sort((a, b) => b.touches - a.touches);
|
|
2181
|
+
|
|
2182
|
+
if (experts.length > 0) {
|
|
2183
|
+
const expLine = experts.slice(0, 5).map((e, i) => {
|
|
2184
|
+
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}.`;
|
|
2185
|
+
const days = Math.floor((Date.now() - new Date(e.lastSeen).getTime()) / 86400000);
|
|
2186
|
+
const ago = days < 1 ? 'today' : days < 7 ? `${days}d ago` : `${Math.floor(days / 7)}w ago`;
|
|
2187
|
+
return `${medal} **${e.handle}** (${e.touches}× · ${ago})`;
|
|
2188
|
+
}).join(' · ');
|
|
2189
|
+
lines.push(`**Experts:** ${expLine}`);
|
|
2190
|
+
} else {
|
|
2191
|
+
lines.push(`**Experts:** _None yet — add \`author\` to \`learn_from_attempts\` calls_`);
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
// ── Related lessons via BM25 on filename tokens ────────────────────
|
|
2195
|
+
const tokens = fp.replace(/[^a-z0-9]/gi, ' ').split(/\s+/).filter(t => t.length >= 4);
|
|
2196
|
+
const lessonQuery = tokens.slice(0, 3).join(' ');
|
|
2197
|
+
const relatedLessons: string[] = [];
|
|
2198
|
+
if (lessonQuery) {
|
|
2199
|
+
try {
|
|
2200
|
+
const hits = await keywordSearch(redis, ['cachly:lesson:best:*'], lessonQuery, 4);
|
|
2201
|
+
for (const h of hits) {
|
|
2202
|
+
const ld = safeJsonParse<{
|
|
2203
|
+
topic?: string; outcome?: string; what_worked?: string;
|
|
2204
|
+
severity?: string; author?: string; visibility?: string;
|
|
2205
|
+
}>(h.content, {});
|
|
2206
|
+
if (ld.visibility === 'private') continue;
|
|
2207
|
+
const emoji = ld.outcome === 'success' ? '✅' : ld.outcome === 'partial' ? '⚠️' : '❌';
|
|
2208
|
+
const sev = ld.severity === 'critical' ? ' 🔴' : ld.severity === 'major' ? ' 🟡' : '';
|
|
2209
|
+
const by = ld.author ? ` · 👤 ${ld.author}` : '';
|
|
2210
|
+
const topic = h.key.replace('cachly:lesson:best:', '');
|
|
2211
|
+
relatedLessons.push(` - ${emoji} \`${topic}\`${sev}${by} — ${(ld.what_worked ?? '').slice(0, 100)}`);
|
|
2212
|
+
}
|
|
2213
|
+
} catch { /* non-critical */ }
|
|
2214
|
+
}
|
|
2215
|
+
// Also: exact file_paths match via lesson scan
|
|
2216
|
+
if (relatedLessons.length < 2) {
|
|
2217
|
+
const scanKeys: string[] = [];
|
|
2218
|
+
const stream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
|
|
2219
|
+
await new Promise<void>((res, rej) => {
|
|
2220
|
+
stream.on('data', (b: string[]) => scanKeys.push(...b));
|
|
2221
|
+
stream.on('end', res); stream.on('error', rej);
|
|
2222
|
+
});
|
|
2223
|
+
for (const k of scanKeys.slice(0, 300)) {
|
|
2224
|
+
const raw = await redis.get(k);
|
|
2225
|
+
if (!raw) continue;
|
|
2226
|
+
const ld = safeJsonParse<{
|
|
2227
|
+
topic?: string; outcome?: string; what_worked?: string;
|
|
2228
|
+
severity?: string; author?: string; visibility?: string; file_paths?: string[];
|
|
2229
|
+
}>(raw, {});
|
|
2230
|
+
if (ld.visibility === 'private') continue;
|
|
2231
|
+
if (!(ld.file_paths ?? []).includes(fp)) continue;
|
|
2232
|
+
const topic = k.replace('cachly:lesson:best:', '');
|
|
2233
|
+
if (relatedLessons.some(l => l.includes(`\`${topic}\``))) continue;
|
|
2234
|
+
const emoji = ld.outcome === 'success' ? '✅' : ld.outcome === 'partial' ? '⚠️' : '❌';
|
|
2235
|
+
const sev = ld.severity === 'critical' ? ' 🔴' : ld.severity === 'major' ? ' 🟡' : '';
|
|
2236
|
+
const by = ld.author ? ` · 👤 ${ld.author}` : '';
|
|
2237
|
+
relatedLessons.push(` - ${emoji} \`${topic}\`${sev}${by} — ${(ld.what_worked ?? '').slice(0, 100)}`);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (relatedLessons.length > 0) {
|
|
2242
|
+
lines.push(`**Related lessons:**`);
|
|
2243
|
+
lines.push(...relatedLessons.slice(0, 5));
|
|
2244
|
+
} else {
|
|
2245
|
+
lines.push(`**Related lessons:** _None yet_`);
|
|
2246
|
+
}
|
|
2247
|
+
lines.push(``);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
lines.push(
|
|
2251
|
+
`---`,
|
|
2252
|
+
`_Attribution builds with \`learn_from_attempts(file_paths=[...], author="...")\`. Run this before committing to track what changed and why._`,
|
|
2253
|
+
);
|
|
2254
|
+
return lines.join('\n');
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// ── team_expertise_map ────────────────────────────────────────────────────
|
|
2258
|
+
// Phase 3B: Full team expertise overview — who knows what, at a glance.
|
|
2259
|
+
case 'team_expertise_map': {
|
|
2260
|
+
const {
|
|
2261
|
+
instance_id,
|
|
2262
|
+
top_n = 20,
|
|
2263
|
+
} = args as { instance_id: string; top_n?: number };
|
|
2264
|
+
|
|
2265
|
+
const redis = await getConnection(instance_id);
|
|
2266
|
+
|
|
2267
|
+
// Scan all person nodes
|
|
2268
|
+
const personKeys: string[] = [];
|
|
2269
|
+
const pStream = redis.scanStream({ match: 'cachly:ckg:node:person:*', count: 200 });
|
|
2270
|
+
await new Promise<void>((res, rej) => {
|
|
2271
|
+
pStream.on('data', (b: string[]) => personKeys.push(...b));
|
|
2272
|
+
pStream.on('end', res); pStream.on('error', rej);
|
|
2273
|
+
});
|
|
2274
|
+
|
|
2275
|
+
if (personKeys.length === 0) {
|
|
2276
|
+
return [
|
|
2277
|
+
`## 🗺️ Team Expertise Map`,
|
|
2278
|
+
``,
|
|
2279
|
+
`No contributors yet.`,
|
|
2280
|
+
``,
|
|
2281
|
+
`Add \`author="handle"\` to any \`learn_from_attempts\` call to start building the map.`,
|
|
2282
|
+
].join('\n');
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
type PersonEntry = { handle: string; lessonCount: number; domains: Map<string, number>; lastActive: string };
|
|
2286
|
+
const people: PersonEntry[] = [];
|
|
2287
|
+
|
|
2288
|
+
for (const pk of personKeys) {
|
|
2289
|
+
const raw = await redis.get(pk);
|
|
2290
|
+
if (!raw) continue;
|
|
2291
|
+
const pn = safeJsonParse<PersonNode | null>(raw, null);
|
|
2292
|
+
if (!pn || pn.type !== 'person') continue;
|
|
2293
|
+
|
|
2294
|
+
// Count authored lessons + collect domains from authored edges
|
|
2295
|
+
const outEdgeKeys = await redis.smembers(`cachly:ckg:idx:from:${pn.id}`);
|
|
2296
|
+
const domainCounts = new Map<string, number>();
|
|
2297
|
+
let lessonCount = 0;
|
|
2298
|
+
for (const ek of outEdgeKeys) {
|
|
2299
|
+
const er = await redis.get(ek);
|
|
2300
|
+
if (!er) continue;
|
|
2301
|
+
const edge = safeJsonParse<CKGEdge | null>(er, null);
|
|
2302
|
+
if (!edge || edge.edgeType !== 'authored') continue;
|
|
2303
|
+
lessonCount++;
|
|
2304
|
+
const domain = edge.to.split(':')[0] ?? 'unknown';
|
|
2305
|
+
domainCounts.set(domain, (domainCounts.get(domain) ?? 0) + 1);
|
|
2306
|
+
}
|
|
2307
|
+
people.push({ handle: pn.handle, lessonCount, domains: domainCounts, lastActive: pn.last_active });
|
|
2308
|
+
}
|
|
2309
|
+
|
|
2310
|
+
people.sort((a, b) => b.lessonCount - a.lessonCount);
|
|
2311
|
+
const topPeople = people.slice(0, top_n);
|
|
2312
|
+
|
|
2313
|
+
const lines = [
|
|
2314
|
+
`## 🗺️ Team Expertise Map`,
|
|
2315
|
+
``,
|
|
2316
|
+
`**${topPeople.length}** contributor${topPeople.length !== 1 ? 's' : ''} tracked:`,
|
|
2317
|
+
``,
|
|
2318
|
+
`| # | Handle | Lessons | Top domains | Last active |`,
|
|
2319
|
+
`|---|--------|---------|-------------|-------------|`,
|
|
2320
|
+
];
|
|
2321
|
+
|
|
2322
|
+
for (const [i, p] of topPeople.entries()) {
|
|
2323
|
+
const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}`;
|
|
2324
|
+
const days = Math.floor((Date.now() - new Date(p.lastActive).getTime()) / 86400000);
|
|
2325
|
+
const ago = days < 1 ? 'today' : days < 7 ? `${days}d ago` : days < 30 ? `${Math.floor(days / 7)}w ago` : `${Math.floor(days / 30)}mo ago`;
|
|
2326
|
+
const domainStr = [...p.domains.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3).map(([d, c]) => `${d}(${c})`).join(', ') || '—';
|
|
2327
|
+
lines.push(`| ${medal} | **${p.handle}** | ${p.lessonCount} | ${domainStr} | ${ago} |`);
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
lines.push(
|
|
2331
|
+
``,
|
|
2332
|
+
`---`,
|
|
2333
|
+
`_Use \`brain_who_knows(topic="...")\` to find the expert for a specific problem._`,
|
|
2334
|
+
);
|
|
2335
|
+
return lines.join('\n');
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2126
2338
|
// ── sync_file_changes ─────────────────────────────────────────────────────
|
|
2127
2339
|
|
|
2128
2340
|
default:
|
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.60';
|
|
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 · 98 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 · 98 MCP tools\x1b[0m');
|
|
1820
1820
|
console.log('');
|
|
1821
1821
|
process.exit(0);
|
|
1822
1822
|
}
|
package/src/tools.ts
CHANGED
|
@@ -575,7 +575,12 @@ const TOOLS = [
|
|
|
575
575
|
},
|
|
576
576
|
author: {
|
|
577
577
|
type: 'string',
|
|
578
|
-
description: 'Name or handle of the person storing this lesson (e.g. "alice", "bob"). Used for Team Telepathy — teammates see each other\'s lessons in session_start.',
|
|
578
|
+
description: 'Name or handle of the person storing this lesson (e.g. "alice", "bob"). Used for Team Telepathy — teammates see each other\'s lessons in session_start. Also powers brain_who_knows and team_expertise_map.',
|
|
579
|
+
},
|
|
580
|
+
visibility: {
|
|
581
|
+
type: 'string',
|
|
582
|
+
enum: ['team', 'private', 'public'],
|
|
583
|
+
description: 'Who can see this lesson. "team" (default) = all team members. "private" = only accessible via exact recall_best_solution, never surfaced in smart_recall or team_recall. "public" = same as team (public syndication planned).',
|
|
579
584
|
},
|
|
580
585
|
},
|
|
581
586
|
required: ['instance_id', 'topic', 'outcome', 'what_worked'],
|
|
@@ -817,6 +822,43 @@ const TOOLS = [
|
|
|
817
822
|
required: ['instance_id', 'topic'],
|
|
818
823
|
},
|
|
819
824
|
},
|
|
825
|
+
{
|
|
826
|
+
name: 'brain_file_map',
|
|
827
|
+
description:
|
|
828
|
+
'Show what cachly knows about a list of files — experts + related lessons per file. ' +
|
|
829
|
+
'Call this before starting work on unfamiliar files, or in sync_file_changes to see what knowledge exists. ' +
|
|
830
|
+
'For each file path: shows who has previously touched it (from learn_from_attempts author+file_paths) ' +
|
|
831
|
+
'and which lessons reference it. ' +
|
|
832
|
+
'Example: brain_file_map(file_paths=["src/auth/jwt.ts"]) → "🥇 alice (3× · today) — related: fix:jwt-expiry".',
|
|
833
|
+
inputSchema: {
|
|
834
|
+
type: 'object',
|
|
835
|
+
properties: {
|
|
836
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
837
|
+
file_paths: {
|
|
838
|
+
type: 'array',
|
|
839
|
+
items: { type: 'string' },
|
|
840
|
+
description: 'File paths to look up (max 10)',
|
|
841
|
+
},
|
|
842
|
+
},
|
|
843
|
+
required: ['instance_id', 'file_paths'],
|
|
844
|
+
},
|
|
845
|
+
},
|
|
846
|
+
{
|
|
847
|
+
name: 'team_expertise_map',
|
|
848
|
+
description:
|
|
849
|
+
'Full team expertise overview — who knows what, at a glance. ' +
|
|
850
|
+
'Returns a ranked table of all contributors with their lesson count, top domains, and last-active date. ' +
|
|
851
|
+
'Use for onboarding (who to ask about X?), retrospectives, or to find knowledge gaps. ' +
|
|
852
|
+
'Built automatically from learn_from_attempts(author=...) calls — no setup needed.',
|
|
853
|
+
inputSchema: {
|
|
854
|
+
type: 'object',
|
|
855
|
+
properties: {
|
|
856
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
857
|
+
top_n: { type: 'number', description: 'Max contributors to show (default: 20)' },
|
|
858
|
+
},
|
|
859
|
+
required: ['instance_id'],
|
|
860
|
+
},
|
|
861
|
+
},
|
|
820
862
|
{
|
|
821
863
|
name: 'sync_file_changes',
|
|
822
864
|
description:
|