@cachly-dev/mcp-server 0.10.14 → 0.10.15

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.
Files changed (44) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
  2. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  4. package/.github/workflows/publish.yml +72 -0
  5. package/CHANGELOG.md +28 -0
  6. package/LAUNCH_TEXTS.md +182 -0
  7. package/LICENSE +202 -0
  8. package/README.md +113 -112
  9. package/dist/__tests__/auth.test.d.ts +1 -0
  10. package/dist/__tests__/auth.test.js +107 -0
  11. package/dist/__tests__/auth.test.js.map +1 -0
  12. package/dist/handlers/brain.js +7 -67
  13. package/dist/handlers/brain.js.map +1 -1
  14. package/dist/handlers/fedbrain.js +93 -44
  15. package/dist/handlers/fedbrain.js.map +1 -1
  16. package/dist/handlers/roadmap.js +8 -2
  17. package/dist/handlers/roadmap.js.map +1 -1
  18. package/dist/handlers/syndicate.js +15 -7
  19. package/dist/handlers/syndicate.js.map +1 -1
  20. package/dist/handlers/team.js +62 -50
  21. package/dist/handlers/team.js.map +1 -1
  22. package/dist/index.js +555 -93
  23. package/dist/index.js.map +1 -1
  24. package/dist/tools.d.ts +3 -54
  25. package/dist/tools.js +8 -62
  26. package/dist/tools.js.map +1 -1
  27. package/glama.json +29 -0
  28. package/llms.txt +155 -0
  29. package/package.json +29 -13
  30. package/scripts/postinstall.js +12 -12
  31. package/server.json +3 -9
  32. package/smithery.yaml +6 -8
  33. package/src/__tests__/auth.test.ts +147 -0
  34. package/src/handlers/brain.ts +7 -64
  35. package/src/handlers/fedbrain.ts +88 -38
  36. package/src/handlers/roadmap.ts +7 -2
  37. package/src/handlers/syndicate.ts +13 -7
  38. package/src/handlers/team.ts +57 -33
  39. package/src/index.ts +599 -106
  40. package/src/tools.ts +8 -66
  41. package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
  42. package/dist/__tests__/brain-predict-failures.test.js +0 -339
  43. package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
  44. package/src/__tests__/brain-predict-failures.test.ts +0 -430
@@ -5,6 +5,7 @@ import type { Redis } from 'ioredis';
5
5
  import { calculateConfidence, confidenceBadge, CONFIDENCE_STALE_VALUE, CONFIDENCE_WARN_VALUE,
6
6
  CONFIDENCE_WARN_DAYS, CONFIDENCE_STALE_DAYS, simpleHash } from '../confidence.js';
7
7
  import type { Instance } from './brain.js';
8
+ import { safeJsonParse } from '../utils.js';
8
9
 
9
10
  type GetConnection = (instanceId: string) => Promise<Redis>;
10
11
  type ApiFetch = <T>(path: string, options?: RequestInit) => Promise<T>;
@@ -53,10 +54,11 @@ export async function handleTeamTool(
53
54
 
54
55
  type Lesson = { topic: string; what_worked: string; outcome: string; file_paths?: string[] };
55
56
  const relevant: string[] = [];
56
- for (const k of lessonKeys) {
57
- const raw = await redis.get(k);
57
+ const sfRaws = lessonKeys.length > 0 ? await redis.mget(...lessonKeys) : [];
58
+ for (const raw of sfRaws) {
58
59
  if (!raw) continue;
59
- const lesson = JSON.parse(raw) as Lesson;
60
+ const lesson = safeJsonParse<Lesson | null>(raw, null);
61
+ if (!lesson) continue;
60
62
  // Match by file_paths stored in lesson OR by topic keywords matching file name
61
63
  const topicWords = lesson.topic.toLowerCase().split(/[:\-_]/);
62
64
  const fileMatches = changed_files.some(f => {
@@ -146,10 +148,12 @@ export async function handleTeamTool(
146
148
  author?: string; tags?: string[];
147
149
  };
148
150
  let lessons: TeamLesson[] = [];
149
- for (const k of lessonKeys) {
150
- const raw = await redis.get(k);
151
- if (!raw) continue;
152
- try { lessons.push(JSON.parse(raw) as TeamLesson); } catch { /* skip */ }
151
+ if (lessonKeys.length > 0) {
152
+ const raws = await redis.mget(...lessonKeys);
153
+ for (const raw of raws) {
154
+ const l = safeJsonParse<TeamLesson | null>(raw ?? null, null);
155
+ if (l) lessons.push(l);
156
+ }
153
157
  }
154
158
 
155
159
  // Filter
@@ -278,10 +282,12 @@ export async function handleTeamTool(
278
282
 
279
283
  type RawLesson = { topic: string; outcome: string; what_worked: string; severity?: string; ts: string; auto_learned?: boolean };
280
284
  const allLessons: RawLesson[] = [];
281
- for (const k of allLessonKeys) {
282
- const raw = await redis.get(k);
283
- if (!raw) continue;
284
- try { allLessons.push(JSON.parse(raw) as RawLesson); } catch { /* skip */ }
285
+ if (allLessonKeys.length > 0) {
286
+ const raws = await redis.mget(...allLessonKeys);
287
+ for (const raw of raws) {
288
+ const l = safeJsonParse<RawLesson | null>(raw ?? null, null);
289
+ if (l) allLessons.push(l);
290
+ }
285
291
  }
286
292
 
287
293
  // Group lessons by top-level category
@@ -367,18 +373,17 @@ export async function handleTeamTool(
367
373
  verified_at?: string; severity?: string; audit_trail?: unknown[];
368
374
  };
369
375
  const lessons: DrLesson[] = [];
370
- for (const k of lessonKeys) {
371
- const raw = await redis.get(k);
372
- if (!raw) continue;
373
- try { lessons.push(JSON.parse(raw) as DrLesson); } catch { /* skip */ }
376
+ if (lessonKeys.length > 0) {
377
+ const raws = await redis.mget(...lessonKeys);
378
+ for (const raw of raws) {
379
+ const l = safeJsonParse<DrLesson | null>(raw ?? null, null);
380
+ if (l) lessons.push(l);
381
+ }
374
382
  }
375
383
 
376
384
  // Last session
377
385
  const lastSessionRaw = await redis.get('cachly:session:last');
378
- let lastSession: { ts: string; summary: string } | null = null;
379
- if (lastSessionRaw) {
380
- try { lastSession = JSON.parse(lastSessionRaw); } catch { /* ignore */ }
381
- }
386
+ const lastSession = safeJsonParse<{ ts: string; summary: string } | null>(lastSessionRaw, null);
382
387
 
383
388
  // Open failures
384
389
  const openFailures = lessons.filter(l => l.outcome === 'failure' || l.outcome === 'partial');
@@ -509,13 +514,24 @@ export async function handleTeamTool(
509
514
  issues.push(`💡 ${unusedLessons.length} lessons never recalled — verify topics match your workflow`);
510
515
  }
511
516
 
512
- const lines = ['🩺 **Brain Doctor Report**', '', ...checks.map(c => ' ' + c), ''];
517
+ const divider = '─'.repeat(48);
518
+ const lines = [
519
+ `🩺 **Brain Doctor**`,
520
+ `\`${divider}\``,
521
+ '',
522
+ '**📊 Health Checks**',
523
+ ...checks.map(c => ' ' + c),
524
+ '',
525
+ ];
513
526
  if (issues.length > 0) {
514
- lines.push('**Issues to fix:**');
515
- for (const i of issues) lines.push(' ' + i);
527
+ lines.push(`**🔧 Action Items** (${issues.length} issue${issues.length > 1 ? 's' : ''})`);
528
+ issues.forEach((item, idx) => lines.push(` ${idx + 1}. ${item}`));
516
529
  lines.push('');
530
+ lines.push(`_Fix the action items above, then re-run \`brain_doctor\` to verify._`);
517
531
  } else {
518
- lines.push(' 🎉 Brain looks healthy! Keep calling session_start/session_end.');
532
+ lines.push('**✅ All checks passed** — Brain is healthy!');
533
+ lines.push('');
534
+ lines.push('_Keep the brain sharp: `session_start` → work → `learn_from_attempts` → `session_end`_');
519
535
  }
520
536
  return lines.join('\n');
521
537
  }
@@ -536,7 +552,11 @@ export async function handleTeamTool(
536
552
  .filter((l): l is NonNullable<typeof l> => l !== null && new Date(l.ts).getTime() <= cutoff)
537
553
  .sort((a, b) => new Date(a.ts).getTime() - new Date(b.ts).getTime());
538
554
 
539
- if (before.length === 0) return `📭 No entries for \`${topic}\` found before **${date}**. Earliest entry: ${new Date(JSON.parse(all[0]).ts).toLocaleDateString('de-DE')}.`;
555
+ if (before.length === 0) {
556
+ const first = safeJsonParse(all[0] ?? null, null as null | { ts?: string });
557
+ const earliest = first?.ts ? new Date(first.ts).toLocaleDateString('de-DE') : 'unknown';
558
+ return `📭 No entries for \`${topic}\` found before **${date}**. Earliest entry: ${earliest}.`;
559
+ }
540
560
 
541
561
  const lines = [
542
562
  `🏺 **Brain Archaeology: \`${topic}\` before ${date}**`,
@@ -563,7 +583,7 @@ export async function handleTeamTool(
563
583
  const raw = await redis.get(depKey);
564
584
  if (!raw) return `📭 No lessons found that depend on \`${dependency}\`.\n\nAdd dependencies via: \`learn_from_attempts(..., depends_on=["${dependency}"])\``;
565
585
 
566
- const topics: string[] = JSON.parse(raw);
586
+ const topics: string[] = safeJsonParse<string[]>(raw, []);
567
587
  const lines = [
568
588
  `🔗 **Causal Chain: \`${dependency}\`** — ${topics.length} dependent lesson${topics.length === 1 ? '' : 's'}`,
569
589
  '',
@@ -572,7 +592,8 @@ export async function handleTeamTool(
572
592
  for (const t of topics) {
573
593
  const lessonRaw = await redis.get(`cachly:lesson:best:${t}`);
574
594
  if (!lessonRaw) { lines.push(` • \`${t}\` _(lesson deleted)_`); continue; }
575
- const lesson = JSON.parse(lessonRaw) as { outcome?: string; severity?: string; needs_review?: boolean };
595
+ const lesson = safeJsonParse(lessonRaw, null as null | { outcome?: string; severity?: string; needs_review?: boolean });
596
+ if (!lesson) { lines.push(` • \`${t}\` _(lesson corrupted)_`); continue; }
576
597
  const emoji = lesson.outcome === 'success' ? '✅' : lesson.outcome === 'partial' ? '⚠️' : '❌';
577
598
  const reviewBadge = lesson.needs_review ? ' 🔍 **needs_review**' : '';
578
599
  lines.push(` ${emoji} \`${t}\` (${lesson.severity ?? 'major'})${reviewBadge}`);
@@ -614,8 +635,8 @@ export async function handleTeamTool(
614
635
  // Preserve recall_count on update
615
636
  const existing = await redis.get(key);
616
637
  if (existing) {
617
- const prev = JSON.parse(existing) as { recall_count?: number };
618
- record.recall_count = prev.recall_count ?? 0;
638
+ const prev = safeJsonParse(existing, null as null | { recall_count?: number });
639
+ record.recall_count = prev?.recall_count ?? 0;
619
640
  }
620
641
  await redis.set(key, JSON.stringify(record));
621
642
  return `🌐 **Global lesson stored**: \`${topic}\`\n\n${lesson}\n\nRecallable from any project via \`global_recall(topic="${topic}")\`.`;
@@ -635,10 +656,12 @@ export async function handleTeamTool(
635
656
 
636
657
  type GlobalLesson = { topic: string; lesson: string; severity?: string; ts: string; recall_count?: number };
637
658
  let lessons: GlobalLesson[] = [];
638
- for (const k of keys) {
639
- const raw = await redis.get(k);
640
- if (!raw) continue;
641
- try { lessons.push(JSON.parse(raw) as GlobalLesson); } catch { /* skip */ }
659
+ if (keys.length > 0) {
660
+ const raws = await redis.mget(...keys);
661
+ for (const raw of raws) {
662
+ const l = safeJsonParse<GlobalLesson | null>(raw ?? null, null);
663
+ if (l) lessons.push(l);
664
+ }
642
665
  }
643
666
 
644
667
  if (topic) {
@@ -655,7 +678,8 @@ export async function handleTeamTool(
655
678
  const k = `cachly:global:lesson:${l.topic}`;
656
679
  const raw = await redis.get(k);
657
680
  if (raw) {
658
- const rec = JSON.parse(raw) as { recall_count?: number };
681
+ const rec = safeJsonParse(raw, null as null | { recall_count?: number });
682
+ if (!rec) continue;
659
683
  rec.recall_count = (rec.recall_count ?? 0) + 1;
660
684
  await redis.set(k, JSON.stringify(rec));
661
685
  }