@cachly-dev/mcp-server 0.10.68 → 0.10.70

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/README.md CHANGED
@@ -22,7 +22,7 @@
22
22
  <img src="https://img.shields.io/badge/GDPR-EU%20servers-green" alt="GDPR: EU servers" />
23
23
  </a>
24
24
  &nbsp;
25
- <img src="https://img.shields.io/badge/101%20MCP%20tools-violet" alt="101 MCP tools" />
25
+ <img src="https://img.shields.io/badge/102%20MCP%20tools-violet" alt="102 MCP tools" />
26
26
  &nbsp;
27
27
  <img src="https://img.shields.io/badge/License-Apache--2.0-yellow" alt="License: Apache-2.0" />
28
28
  </p>
package/dist/src/ckg.d.ts CHANGED
@@ -31,6 +31,15 @@ export type FileNode = {
31
31
  count: number;
32
32
  ts: string;
33
33
  };
34
+ export type ServiceNode = {
35
+ id: string;
36
+ name: string;
37
+ kind: 'service' | 'system';
38
+ domain: string;
39
+ type: 'service';
40
+ count: number;
41
+ last_active: string;
42
+ };
34
43
  export declare function ckgSlug(text: string): string;
35
44
  /** Extract 1-3 significant keywords from free text for a problem concept */
36
45
  export declare function extractProblemConcept(text: string): string | null;
@@ -39,5 +48,7 @@ export declare function ckgUpsertNode(redis: Redis, id: string, domain: string,
39
48
  export declare function ckgUpsertPersonNode(redis: Redis, handle: string, domain: string): Promise<string>;
40
49
  /** Upsert a File node; returns the node id ("file:{slug}"). */
41
50
  export declare function ckgUpsertFileNode(redis: Redis, filePath: string): Promise<string>;
51
+ /** Upsert a Service/System node; returns the node id ("service:{slug}"). */
52
+ export declare function ckgUpsertServiceNode(redis: Redis, name: string, domain: string, kind?: 'service' | 'system'): Promise<string>;
42
53
  export declare function ckgUpdateEdge(redis: Redis, from: string, edgeType: string, to: string, success: boolean, partial?: boolean): Promise<void>;
43
54
  export declare function ckgRecordCollaboration(redis: Redis, fileId: string, personId: string): Promise<void>;
package/dist/src/ckg.js CHANGED
@@ -49,6 +49,25 @@ export async function ckgUpsertFileNode(redis, filePath) {
49
49
  await redis.set(key, JSON.stringify(node));
50
50
  return id;
51
51
  }
52
+ /** Upsert a Service/System node; returns the node id ("service:{slug}"). */
53
+ export async function ckgUpsertServiceNode(redis, name, domain, kind = 'service') {
54
+ const id = `service:${ckgSlug(name)}`;
55
+ const key = `cachly:ckg:node:${id}`;
56
+ const raw = await redis.get(key);
57
+ const node = raw
58
+ ? JSON.parse(raw)
59
+ : { id, name, kind, domain, type: 'service', count: 0, last_active: new Date().toISOString() };
60
+ node.count = (node.count || 0) + 1;
61
+ node.last_active = new Date().toISOString();
62
+ if (!node.domain)
63
+ node.domain = domain;
64
+ // A node may start life as a 'service' and later be classified as 'system' (or vice
65
+ // versa) by an explicit caller — honour the latest non-default classification.
66
+ if (kind === 'system')
67
+ node.kind = 'system';
68
+ await redis.set(key, JSON.stringify(node));
69
+ return id;
70
+ }
52
71
  export async function ckgUpdateEdge(redis, from, edgeType, to, success, partial = false) {
53
72
  const key = `cachly:ckg:edge:${from}:${edgeType}:${to}`;
54
73
  const raw = await redis.get(key);
@@ -1 +1 @@
1
- {"version":3,"file":"ckg.js","sourceRoot":"","sources":["../../src/ckg.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ckg.js","sourceRoot":"","sources":["../../src/ckg.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,26 @@
1
1
  import { execSync } from 'node:child_process';
2
2
  import { calculateConfidence, confidenceBadge, STRUCTURED_TEMPLATES, CONFIDENCE_WARN_VALUE, CONFIDENCE_STALE_VALUE, CONFIDENCE_WARN_DAYS } from '../confidence.js';
3
- import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge, ckgUpsertPersonNode, ckgUpsertFileNode, ckgRecordCollaboration } from '../ckg.js';
3
+ import { ckgSlug, extractProblemConcept, ckgUpsertNode, ckgUpdateEdge, ckgUpsertPersonNode, ckgUpsertFileNode, ckgRecordCollaboration, ckgUpsertServiceNode } from '../ckg.js';
4
4
  import { safeJsonParse, scanKeys } from '../utils.js';
5
5
  import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _indexVocab } from '../search.js';
6
6
  import { rerankByQuality } from '../rerank.js';
7
7
  import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
8
8
  // ── Changelog (shown once per version in session_start) ──────────────────────
9
- const MCP_VERSION = '0.10.68';
9
+ const MCP_VERSION = '0.10.70';
10
10
  const WHATS_NEW = {
11
+ '0.10.70': [
12
+ `🛰️ **Service/System nodes in the graph (Phase 3)** — the brain now models running systems`,
13
+ ` 🆕 \`brain_service_map(service="prometheus")\` — who owns it + every failure/fix known`,
14
+ ` 🏷️ Tag lessons with \`service="..."\` (\`service_kind="system"\` for infra) — person→operates, file→runs_in edges`,
15
+ ` 🚨 Incident triage: a restarting pod → instant "who knows this and what's broken before"`,
16
+ ` 📊 102 MCP tools`,
17
+ ],
18
+ '0.10.69': [
19
+ `📁 **Personalized context-aware recall (Phase 3)** — pass \`context_files\` to \`smart_recall\``,
20
+ ` 🎯 Lessons learned on your current files bubble up — even when the query doesn't name the file`,
21
+ ` 🏷️ \`📁 context match\` badge on every file-boosted result so you know WHY it ranked up`,
22
+ ` ✅ Works alongside keyword, semantic, CKG, and governance signals — a 6th ranking dimension`,
23
+ ],
11
24
  '0.10.68': [
12
25
  `🤝 **Collaboration graph (Phase 3)** — person↔person edges from shared files`,
13
26
  ` 👥 \`brain_who_knows\` now shows who the top expert frequently works with`,
@@ -81,12 +94,12 @@ export const BRAIN_TOOL_NAMES = new Set([
81
94
  'learn_from_attempts', 'recall_best_solution', 'smart_recall',
82
95
  'session_start', 'session_end', 'session_ping', 'session_handoff', 'auto_learn_session',
83
96
  'brain_who_knows', 'brain_file_map', 'team_expertise_map',
84
- 'skill_gaps', 'brain_coverage', 'brain_metrics',
97
+ 'skill_gaps', 'brain_coverage', 'brain_metrics', 'brain_service_map',
85
98
  ]);
86
99
  export async function handleBrainTool(name, args, getConnection, apiFetch) {
87
100
  switch (name) {
88
101
  case 'learn_from_attempts': {
89
- const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], author = '', visibility = 'team', } = args;
102
+ const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], author = '', visibility = 'team', service = '', service_kind = 'service', } = args;
90
103
  const redis = await getConnection(instance_id);
91
104
  const ts = new Date().toISOString();
92
105
  // ── Structured template hints ──────────────────────────────────────────
@@ -204,6 +217,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
204
217
  tags,
205
218
  depends_on,
206
219
  ...(author ? { author } : {}),
220
+ ...(service ? { service } : {}),
207
221
  visibility,
208
222
  recall_count: recallCount,
209
223
  ts,
@@ -372,6 +386,30 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
372
386
  }
373
387
  catch { /* non-critical */ }
374
388
  }
389
+ // ── Phase 3: Service/System nodes ─────────────────────────────────────────
390
+ // When a lesson names the service/system it concerns (e.g. "prometheus",
391
+ // "cachly-web"), wire it into the graph: person→operates→service,
392
+ // concept→affects→service, file→runs_in→service. This is what lets
393
+ // brain_service_map answer "who owns X and what's gone wrong with X before".
394
+ if (service && typeof service === 'string' && service.trim()) {
395
+ try {
396
+ const domain = topic.split(':')[0] ?? 'unknown';
397
+ const kind = service_kind === 'system' ? 'system' : 'service';
398
+ const serviceId = await ckgUpsertServiceNode(redis, service.trim(), domain, kind);
399
+ const conceptId = ckgSlug(topic);
400
+ // concept → affects → service (outcome decides edge confidence)
401
+ await ckgUpdateEdge(redis, conceptId, 'affects', serviceId, outcome === 'success', outcome === 'partial');
402
+ if (author) {
403
+ const personId = `person:${ckgSlug(author)}`;
404
+ await ckgUpdateEdge(redis, personId, 'operates', serviceId, true);
405
+ }
406
+ for (const fp of file_paths.slice(0, 8)) {
407
+ const fileId = `file:${ckgSlug(fp)}`;
408
+ await ckgUpdateEdge(redis, fileId, 'runs_in', serviceId, true);
409
+ }
410
+ }
411
+ catch { /* non-critical */ }
412
+ }
375
413
  // ── Ambient team propagation: if author is set + outcome is success,
376
414
  // auto-store as a team lesson so knowledge is shared without a separate
377
415
  // team_learn call. Tags gain 'team'; the lesson is attributed to the author.
@@ -527,7 +565,10 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
527
565
  return `🔍 **Partial matches for \`${topic}\`:**\n\n${results.join('\n\n')}`;
528
566
  }
529
567
  case 'smart_recall': {
530
- const { instance_id, query, threshold = 0.78, author: requester = '', } = args;
568
+ const { instance_id, query, threshold = 0.78, author: requester = '', context_files: rawContextFiles = [], } = args;
569
+ const contextFiles = Array.isArray(rawContextFiles)
570
+ ? rawContextFiles.filter((f) => typeof f === 'string')
571
+ : [];
531
572
  const redis = await getConnection(instance_id);
532
573
  // ── Layer 1: Keyword search across ALL brain data (always works, no embedding) ──
533
574
  const rawMatches = await keywordSearch(redis, ['cachly:ctx:*', 'cachly:lesson:best:*', 'cachly:idx:*'], query, 10);
@@ -682,6 +723,26 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
682
723
  }
683
724
  }
684
725
  catch { /* non-critical — keyword + semantic always available */ }
726
+ // ── Layer 4: File-context personalization ────────────────────────────────
727
+ // When the caller provides the files they are currently working on, lessons
728
+ // that were learned in the context of those files get a score boost. This
729
+ // surfaces file-specific institutional knowledge even when the query words
730
+ // don't mention the file name — the structural advantage of a graph brain.
731
+ if (contextFiles.length > 0) {
732
+ const ctxSet = new Set(contextFiles.map(f => f.replace(/\\/g, '/')));
733
+ for (const r of hybridMap.values()) {
734
+ if (!r.key.startsWith('cachly:lesson:best:'))
735
+ continue;
736
+ const ld = safeJsonParse(r.content, {});
737
+ const fps = Array.isArray(ld.file_paths)
738
+ ? ld.file_paths.filter((f) => typeof f === 'string').map(f => f.replace(/\\/g, '/'))
739
+ : [];
740
+ if (fps.some(f => ctxSet.has(f))) {
741
+ r.hybridScore *= 1.15;
742
+ r.contextBoost = true;
743
+ }
744
+ }
745
+ }
685
746
  // Filter private lessons — they are only accessible via exact recall_best_solution
686
747
  const hybridResults = [...hybridMap.values()]
687
748
  .filter(r => {
@@ -702,6 +763,11 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
702
763
  if (crossAuthorThisCall > 0) {
703
764
  lines.push(`> 👥 **Team knowledge reuse** — ${crossAuthorThisCall} of these lesson${crossAuthorThisCall !== 1 ? 's were' : ' was'} written by a teammate. This is the value only a shared brain delivers.\n`);
704
765
  }
766
+ // File-context personalization banner
767
+ const ctxBoosted = hybridResults.filter(r => r.contextBoost).length;
768
+ if (ctxBoosted > 0) {
769
+ lines.push(`> 📁 **Personalized** — ${ctxBoosted} lesson${ctxBoosted !== 1 ? 's' : ''} boosted because ${ctxBoosted !== 1 ? 'they match' : 'it matches'} your current file context (${contextFiles.slice(0, 3).join(', ')}${contextFiles.length > 3 ? ', …' : ''})\n`);
770
+ }
705
771
  // Show sub-query info if multi-topic was detected
706
772
  const subQueries = splitMultiQuery(query);
707
773
  if (subQueries.length > 1) {
@@ -759,13 +825,14 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
759
825
  if (ld.author)
760
826
  authorBadge = ` · 👤 ${ld.author}`;
761
827
  }
828
+ const contextBadge = r.contextBoost ? ` · 📁 context match` : '';
762
829
  const scorePart = r.matchType === 'hybrid'
763
830
  ? `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, 🔀 hybrid`
764
831
  : r.matchType === 'semantic'
765
832
  ? `sem: ${((r.semScore ?? 0) * 100).toFixed(0)}%, 🎯 semantic`
766
833
  : `BM25: ${(r.bm25Score ?? 0).toFixed(2)}, matched: ${r.matchedWords?.join(', ')}`;
767
834
  const preview = r.content.slice(0, 400).replace(/\n/g, ' ');
768
- lines.push(`**${label}**${authorBadge} _(${scorePart})_`);
835
+ lines.push(`**${label}**${authorBadge}${contextBadge} _(${scorePart})_`);
769
836
  lines.push(`> ${preview}${r.content.length > 400 ? '…' : ''}\n`);
770
837
  }
771
838
  }
@@ -2382,6 +2449,120 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
2382
2449
  ].filter(Boolean);
2383
2450
  return lines.join('\n');
2384
2451
  }
2452
+ // ── brain_service_map ───────────────────────────────────────────────────────
2453
+ // Phase 3: "Who owns this service, and what's gone wrong with it before?"
2454
+ // Bridges a running system (e.g. a restarting prometheus pod) to the people who
2455
+ // operate it, the files that run in it, and the lessons learned operating it.
2456
+ case 'brain_service_map': {
2457
+ const { instance_id, service, } = args;
2458
+ if (!service || typeof service !== 'string' || !service.trim()) {
2459
+ return '⚠️ `brain_service_map` requires a non-empty `service`. Example: `brain_service_map(service="prometheus")`.';
2460
+ }
2461
+ const redis = await getConnection(instance_id);
2462
+ const serviceId = `service:${ckgSlug(service.trim())}`;
2463
+ const nodeRaw = await redis.get(`cachly:ckg:node:${serviceId}`).catch(() => null);
2464
+ const node = nodeRaw ? safeJsonParse(nodeRaw, null) : null;
2465
+ // Inbound edges: person→operates, concept→affects, file→runs_in
2466
+ const inbound = await redis.smembers(`cachly:ckg:idx:to:${serviceId}`).catch(() => []);
2467
+ const operators = [];
2468
+ const files = [];
2469
+ const concepts = [];
2470
+ for (const ek of inbound) {
2471
+ const er = await redis.get(ek).catch(() => null);
2472
+ if (!er)
2473
+ continue;
2474
+ const edge = safeJsonParse(er, null);
2475
+ if (!edge)
2476
+ continue;
2477
+ if (edge.edgeType === 'operates') {
2478
+ const pr = await redis.get(`cachly:ckg:node:${edge.from}`).catch(() => null);
2479
+ const pn = pr ? safeJsonParse(pr, null) : null;
2480
+ if (pn?.handle)
2481
+ operators.push({ handle: pn.handle, trials: edge.trials });
2482
+ }
2483
+ else if (edge.edgeType === 'runs_in') {
2484
+ const fr = await redis.get(`cachly:ckg:node:${edge.from}`).catch(() => null);
2485
+ const fn = fr ? safeJsonParse(fr, null) : null;
2486
+ if (fn?.path)
2487
+ files.push({ path: fn.path, trials: edge.trials });
2488
+ }
2489
+ else if (edge.edgeType === 'affects') {
2490
+ concepts.push({ id: edge.from, confidence: edge.confidence });
2491
+ }
2492
+ }
2493
+ operators.sort((a, b) => b.trials - a.trials);
2494
+ files.sort((a, b) => b.trials - a.trials);
2495
+ concepts.sort((a, b) => b.confidence - a.confidence);
2496
+ const lessons = [];
2497
+ const lessonKeys = await scanKeys(redis, 'cachly:lesson:best:*', { max: 1500 });
2498
+ const svcLower = service.trim().toLowerCase();
2499
+ for (const k of lessonKeys.slice(0, 400)) {
2500
+ const raw = await redis.get(k).catch(() => null);
2501
+ if (!raw)
2502
+ continue;
2503
+ const ld = safeJsonParse(raw, {});
2504
+ if (ld.visibility === 'private')
2505
+ continue;
2506
+ if ((ld.service ?? '').toLowerCase() !== svcLower)
2507
+ continue;
2508
+ lessons.push({ ...ld, topic: k.replace('cachly:lesson:best:', '') });
2509
+ }
2510
+ if (!node && lessons.length === 0 && operators.length === 0) {
2511
+ return [
2512
+ `## 🛰️ Service Map — \`${service}\``,
2513
+ ``,
2514
+ `Nothing known about this service yet.`,
2515
+ ``,
2516
+ `Tag lessons with the service they concern so the brain can build this map:`,
2517
+ `\`learn_from_attempts(topic="...", service="${service}", author="you", file_paths=[...])\``,
2518
+ ].join('\n');
2519
+ }
2520
+ const kindLabel = node?.kind === 'system' ? '🖥️ system' : '🛰️ service';
2521
+ const lines = [
2522
+ `## ${node?.kind === 'system' ? '🖥️' : '🛰️'} Service Map — \`${service}\``,
2523
+ ``,
2524
+ node ? `_${kindLabel} · ${node.count} lesson${node.count !== 1 ? 's' : ''} referenced · domain: ${node.domain}_` : `_No graph node yet — built from tagged lessons below._`,
2525
+ ``,
2526
+ ];
2527
+ // Operators (who owns it)
2528
+ if (operators.length > 0) {
2529
+ const owners = operators.slice(0, 5).map((o, i) => {
2530
+ const medal = i === 0 ? '🥇' : i === 1 ? '🥈' : i === 2 ? '🥉' : `${i + 1}.`;
2531
+ return `${medal} **${o.handle}** (${o.trials}×)`;
2532
+ }).join(' · ');
2533
+ lines.push(`**Operators:** ${owners}`, ``);
2534
+ }
2535
+ else {
2536
+ lines.push(`**Operators:** _None attributed — add \`author\` when learning about this service_`, ``);
2537
+ }
2538
+ // Recent failures first — the triage gold for an incident
2539
+ const failures = lessons.filter(l => l.outcome === 'failure');
2540
+ const successes = lessons.filter(l => l.outcome !== 'failure');
2541
+ if (failures.length > 0) {
2542
+ lines.push(`**⚠️ Known failures (${failures.length}):**`);
2543
+ for (const l of failures.slice(0, 6)) {
2544
+ const sev = l.severity === 'critical' ? ' 🔴' : l.severity === 'major' ? ' 🟡' : '';
2545
+ const by = l.author ? ` · 👤 ${l.author}` : '';
2546
+ lines.push(` - ❌ \`${l.topic}\`${sev}${by} — ${(l.what_worked ?? '').slice(0, 100)}`);
2547
+ }
2548
+ lines.push(``);
2549
+ }
2550
+ if (successes.length > 0) {
2551
+ lines.push(`**✅ Proven fixes (${successes.length}):**`);
2552
+ for (const l of successes.slice(0, 6)) {
2553
+ const emoji = l.outcome === 'partial' ? '⚠️' : '✅';
2554
+ const by = l.author ? ` · 👤 ${l.author}` : '';
2555
+ lines.push(` - ${emoji} \`${l.topic}\`${by} — ${(l.what_worked ?? '').slice(0, 100)}`);
2556
+ }
2557
+ lines.push(``);
2558
+ }
2559
+ // Files that run in this service
2560
+ if (files.length > 0) {
2561
+ lines.push(`**Files in this service:** ${files.slice(0, 8).map(f => `\`${f.path}\``).join(', ')}`, ``);
2562
+ }
2563
+ lines.push(`---`, `_Tag lessons with \`service="${service}"\` to keep this map current. For infra, pass \`service_kind="system"\`._`);
2564
+ return lines.join('\n');
2565
+ }
2385
2566
  // ── sync_file_changes ─────────────────────────────────────────────────────
2386
2567
  default:
2387
2568
  return null;