@cachly-dev/mcp-server 0.10.26 → 0.10.40
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/.env.test.example +23 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +50 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- package/.github/workflows/publish.yml +72 -0
- package/CHANGELOG.md +252 -0
- package/LAUNCH_TEXTS.md +182 -0
- package/LICENSE +202 -0
- package/README.md +142 -112
- package/dist/__tests__/auth.test.d.ts +1 -0
- package/dist/__tests__/auth.test.js +107 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/e2e/api-auth.test.d.ts +7 -0
- package/dist/__tests__/e2e/api-auth.test.js +162 -0
- package/dist/__tests__/e2e/api-auth.test.js.map +1 -0
- package/dist/__tests__/e2e/api-health.test.d.ts +7 -0
- package/dist/__tests__/e2e/api-health.test.js +95 -0
- package/dist/__tests__/e2e/api-health.test.js.map +1 -0
- package/dist/__tests__/e2e/cli.test.d.ts +10 -0
- package/dist/__tests__/e2e/cli.test.js +204 -0
- package/dist/__tests__/e2e/cli.test.js.map +1 -0
- package/dist/__tests__/e2e/config.d.ts +35 -0
- package/dist/__tests__/e2e/config.js +53 -0
- package/dist/__tests__/e2e/config.js.map +1 -0
- package/dist/__tests__/e2e/funnel.test.d.ts +13 -0
- package/dist/__tests__/e2e/funnel.test.js +90 -0
- package/dist/__tests__/e2e/funnel.test.js.map +1 -0
- package/dist/__tests__/e2e/mcp-tools.test.d.ts +7 -0
- package/dist/__tests__/e2e/mcp-tools.test.js +129 -0
- package/dist/__tests__/e2e/mcp-tools.test.js.map +1 -0
- package/dist/__tests__/e2e/provisioning.test.d.ts +15 -0
- package/dist/__tests__/e2e/provisioning.test.js +123 -0
- package/dist/__tests__/e2e/provisioning.test.js.map +1 -0
- package/dist/handlers/brain.js +119 -82
- package/dist/handlers/brain.js.map +1 -1
- package/dist/handlers/fedbrain.js +93 -44
- package/dist/handlers/fedbrain.js.map +1 -1
- package/dist/handlers/roadmap.js +8 -2
- package/dist/handlers/roadmap.js.map +1 -1
- package/dist/handlers/syndicate.js +15 -7
- package/dist/handlers/syndicate.js.map +1 -1
- package/dist/handlers/team.js +62 -50
- package/dist/handlers/team.js.map +1 -1
- package/dist/index.js +1264 -234
- package/dist/index.js.map +1 -1
- package/dist/tools.d.ts +3 -54
- package/dist/tools.js +8 -62
- package/dist/tools.js.map +1 -1
- package/eslint.config.js +16 -0
- package/glama.json +36 -0
- package/llms.txt +155 -0
- package/package.json +38 -14
- package/scripts/e2e-run.sh +160 -0
- package/scripts/postinstall.js +12 -12
- package/server.json +3 -9
- package/smithery.yaml +15 -8
- package/src/__tests__/auth.test.ts +147 -0
- package/src/__tests__/e2e/api-auth.test.ts +182 -0
- package/src/__tests__/e2e/api-health.test.ts +103 -0
- package/src/__tests__/e2e/cli.test.ts +228 -0
- package/src/__tests__/e2e/config.ts +53 -0
- package/src/__tests__/e2e/funnel.test.ts +99 -0
- package/src/__tests__/e2e/mcp-tools.test.ts +191 -0
- package/src/__tests__/e2e/provisioning.test.ts +128 -0
- package/src/handlers/brain.ts +118 -78
- package/src/handlers/fedbrain.ts +88 -38
- package/src/handlers/roadmap.ts +7 -2
- package/src/handlers/syndicate.ts +13 -7
- package/src/handlers/team.ts +57 -33
- package/src/index.ts +1312 -257
- package/src/tools.ts +8 -66
- package/vitest.config.ts +12 -0
- package/dist/__tests__/brain-predict-failures.test.d.ts +0 -17
- package/dist/__tests__/brain-predict-failures.test.js +0 -339
- package/dist/__tests__/brain-predict-failures.test.js.map +0 -1
- package/src/__tests__/brain-predict-failures.test.ts +0 -430
package/dist/handlers/brain.js
CHANGED
|
@@ -5,13 +5,15 @@ import { safeJsonParse } from '../utils.js';
|
|
|
5
5
|
import { keywordSearch, tokenize, splitMultiQuery, levenshtein, indexVocab as _indexVocab } from '../search.js';
|
|
6
6
|
import { computeEmbedding, hasEmbedProvider } from '../embeddings.js';
|
|
7
7
|
// ── Changelog (shown once per version in session_start) ──────────────────────
|
|
8
|
-
const MCP_VERSION = '0.10.
|
|
8
|
+
const MCP_VERSION = '0.10.0';
|
|
9
9
|
const WHATS_NEW = {
|
|
10
|
-
'0.10.
|
|
10
|
+
'0.10.0': [
|
|
11
11
|
`🆕 **What's new in v${MCP_VERSION}:**`,
|
|
12
|
-
`
|
|
13
|
-
`
|
|
14
|
-
` ✅
|
|
12
|
+
` ✅ Zero-friction setup — auto-provisions your Brain, no manual config needed`,
|
|
13
|
+
` ✅ 10× faster briefings — batch Redis fetches replace per-key loops`,
|
|
14
|
+
` ✅ Crash-proof data reads — corrupted cache entries no longer break tools`,
|
|
15
|
+
` ✅ \`session_end\` warns when called without an active session`,
|
|
16
|
+
` ✅ \`get_api_status\` now shows live Redis ping + all instance statuses`,
|
|
15
17
|
],
|
|
16
18
|
};
|
|
17
19
|
export const BRAIN_TOOL_NAMES = new Set([
|
|
@@ -270,20 +272,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
270
272
|
}
|
|
271
273
|
}
|
|
272
274
|
catch { /* CKG updates are non-critical */ }
|
|
273
|
-
// ── Auto-pin critical lessons ──────────────────────────────────────────
|
|
274
|
-
// severity=critical → always pin automatically so the lesson
|
|
275
|
-
// appears in every session_start without the user ever calling pin_lesson.
|
|
276
|
-
let autoPinNote = '';
|
|
277
|
-
if (severity === 'critical' && (outcome === 'success' || outcome === 'partial')) {
|
|
278
|
-
try {
|
|
279
|
-
const alreadyPinned = await redis.exists(`cachly:lesson:pin:${topic}`);
|
|
280
|
-
if (!alreadyPinned) {
|
|
281
|
-
await redis.set(`cachly:lesson:pin:${topic}`, '1');
|
|
282
|
-
autoPinNote = `📌 Auto-pinned (critical severity) — will appear in every session_start.`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
catch { /* non-critical */ }
|
|
286
|
-
}
|
|
287
275
|
const emoji = outcome === 'success' ? '✅' : outcome === 'partial' ? '⚠️' : '❌';
|
|
288
276
|
const sevEmoji = severity === 'critical' ? '🔴' : severity === 'major' ? '🟡' : '🟢';
|
|
289
277
|
const action = isUpdate ? 'updated' : 'stored';
|
|
@@ -304,7 +292,6 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
304
292
|
depends_on.length > 0
|
|
305
293
|
? `🔗 Depends on: ${depends_on.map(d => `\`${d}\``).join(', ')} → trace with \`trace_dependency\``
|
|
306
294
|
: '',
|
|
307
|
-
autoPinNote,
|
|
308
295
|
...templateWarnings,
|
|
309
296
|
...iWasWrongWarning,
|
|
310
297
|
].filter(l => l !== '').join('\n');
|
|
@@ -636,19 +623,70 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
636
623
|
const isFirstSession = !lastSession && lessons.length === 0 && ctxCount === 0;
|
|
637
624
|
if (isFirstSession) {
|
|
638
625
|
lines.push('🎉 **Welcome! Your AI Brain is live.**', '');
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
626
|
+
if (workspace_path) {
|
|
627
|
+
lines.push(`🚀 **Auto-bootstrapping from your git history...** (this takes a few seconds)`);
|
|
628
|
+
lines.push(` Your brain will learn from your recent commits — no setup needed.`, '');
|
|
629
|
+
}
|
|
630
|
+
else {
|
|
631
|
+
lines.push('It learns from your work automatically. After your first session it will look like this:', '');
|
|
632
|
+
lines.push(' ✅ `api:auth` — Bearer token in header, not cookie; 401 on missing scope');
|
|
633
|
+
lines.push(' ✅ `database:migrations` — always run migrations before deploy');
|
|
634
|
+
lines.push(' ⚠️ `docker:build` — ARG changes bust all subsequent cache layers');
|
|
635
|
+
lines.push('');
|
|
636
|
+
lines.push('**Tip:** Pass `workspace_path` to `session_start` to auto-learn from git history instantly.');
|
|
637
|
+
lines.push('');
|
|
638
|
+
}
|
|
639
|
+
lines.push('**Your brain grows automatically:**');
|
|
640
|
+
lines.push(' • End each session → `session_end(summary="What I did")` — auto-learns from git commits');
|
|
641
|
+
lines.push(' • After fixing bugs → `learn_from_attempts(topic="...", outcome="success", what_worked="...")`');
|
|
648
642
|
lines.push('');
|
|
649
|
-
lines.push('💡 Run `brain_doctor` for a
|
|
643
|
+
lines.push('💡 Run `brain_doctor` for a health-check and personalised tips.');
|
|
650
644
|
lines.push('');
|
|
651
645
|
}
|
|
646
|
+
// ── Team-virality: first-team-briefing wow moment ────────────────────
|
|
647
|
+
// When a user joins a team brain (has team lessons from colleagues but
|
|
648
|
+
// has never been briefed on them), show a dedicated "Welcome to your
|
|
649
|
+
// team's brain" section. Only fires once per user.
|
|
650
|
+
if (author && !isFirstSession) {
|
|
651
|
+
try {
|
|
652
|
+
const briefingKey = `cachly:team:first_briefing:${author}`;
|
|
653
|
+
const alreadyBriefed = await redis.get(briefingKey);
|
|
654
|
+
if (!alreadyBriefed) {
|
|
655
|
+
const teamLessons = lessons.filter(l => l.author && l.author !== author);
|
|
656
|
+
if (teamLessons.length > 0) {
|
|
657
|
+
// Mark briefed so this only fires once
|
|
658
|
+
await redis.set(briefingKey, '1', 'EX', 365 * 86400);
|
|
659
|
+
const byAuthor = new Map();
|
|
660
|
+
for (const l of teamLessons) {
|
|
661
|
+
const a = l.author;
|
|
662
|
+
if (!byAuthor.has(a))
|
|
663
|
+
byAuthor.set(a, []);
|
|
664
|
+
byAuthor.get(a).push(l);
|
|
665
|
+
}
|
|
666
|
+
lines.push('');
|
|
667
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
668
|
+
lines.push('🤝 **Your team\'s AI brain has been briefing you.**');
|
|
669
|
+
lines.push('');
|
|
670
|
+
lines.push(`Your teammates have already solved problems you\'re about to hit:`);
|
|
671
|
+
lines.push('');
|
|
672
|
+
for (const [teamAuthor, tls] of byAuthor) {
|
|
673
|
+
lines.push(` 👤 **${teamAuthor}** fixed ${tls.length} thing${tls.length > 1 ? 's' : ''}:`);
|
|
674
|
+
for (const l of tls.slice(0, 2)) {
|
|
675
|
+
const emoji = l.outcome === 'success' ? '✅' : '⚠️';
|
|
676
|
+
lines.push(` ${emoji} \`${l.topic}\` — ${l.what_worked.slice(0, 90)}`);
|
|
677
|
+
}
|
|
678
|
+
if (tls.length > 2)
|
|
679
|
+
lines.push(` … and ${tls.length - 2} more lessons`);
|
|
680
|
+
}
|
|
681
|
+
lines.push('');
|
|
682
|
+
lines.push(`💡 Use \`team_learn\` after your next fix to pay it forward.`);
|
|
683
|
+
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
684
|
+
lines.push('');
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
catch { /* team briefing errors must never break session_start */ }
|
|
689
|
+
}
|
|
652
690
|
// Handoff from previous window (if any)
|
|
653
691
|
const handoffRaw = await redis.get('cachly:session:handoff');
|
|
654
692
|
if (handoffRaw) {
|
|
@@ -831,47 +869,34 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
831
869
|
}
|
|
832
870
|
lines.push('');
|
|
833
871
|
}
|
|
834
|
-
//
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
const
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
const pinLessonRaws = await redis.mget(...pinnedTopics.map(t => `cachly:lesson:best:${t}`));
|
|
846
|
-
const pinLines = [];
|
|
847
|
-
for (const raw of pinLessonRaws) {
|
|
848
|
-
if (!raw)
|
|
849
|
-
continue;
|
|
850
|
-
const l = safeJsonParse(raw, null);
|
|
851
|
-
if (!l)
|
|
852
|
-
continue;
|
|
872
|
+
// Top lessons — sorted by recall_count desc (most-used = most proven value).
|
|
873
|
+
// If focus is set, focus-matched lessons already shown above; show remaining here.
|
|
874
|
+
if (lessons.length > 0) {
|
|
875
|
+
const byRecall = [...lessons].sort((a, b) => (b.recall_count ?? 0) - (a.recall_count ?? 0));
|
|
876
|
+
const topRecalled = byRecall.filter(l => (l.recall_count ?? 0) > 0).slice(0, 3);
|
|
877
|
+
const remaining = (focusLessons.length > 0 ? lessons.filter(l => !focusLessons.includes(l)) : lessons)
|
|
878
|
+
.filter(l => !topRecalled.includes(l))
|
|
879
|
+
.slice(0, focusLessons.length > 0 ? 3 : 4);
|
|
880
|
+
if (topRecalled.length > 0) {
|
|
881
|
+
lines.push(`🏆 **Most valuable** (recalled ${topRecalled[0].recall_count}× before):`);
|
|
882
|
+
for (const l of topRecalled) {
|
|
853
883
|
const emoji = l.outcome === 'success' ? '✅' : l.outcome === 'partial' ? '⚠️' : '❌';
|
|
854
884
|
const sev = l.severity === 'critical' ? '🔴' : l.severity === 'major' ? '🟡' : '';
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (pinLines.length > 0) {
|
|
858
|
-
lines.push(`📌 **Pinned lessons (always shown):**`);
|
|
859
|
-
lines.push(...pinLines);
|
|
860
|
-
lines.push('');
|
|
885
|
+
const rct = l.recall_count && l.recall_count > 1 ? ` _(${l.recall_count}× recalled)_` : '';
|
|
886
|
+
lines.push(` ${emoji}${sev} \`${l.topic}\`${rct} — ${l.what_worked.slice(0, 100)}`);
|
|
861
887
|
}
|
|
888
|
+
lines.push('');
|
|
862
889
|
}
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
lines.push(` ${emoji}${sev} \`${l.topic}\` — ${l.what_worked.slice(0, 90)}`);
|
|
890
|
+
if (remaining.length > 0) {
|
|
891
|
+
const header = topRecalled.length > 0 ? `🕐 **Recent:**` : `🕐 **Recent lessons:**`;
|
|
892
|
+
lines.push(header);
|
|
893
|
+
for (const l of remaining) {
|
|
894
|
+
const emoji = l.outcome === 'success' ? '✅' : l.outcome === 'partial' ? '⚠️' : '❌';
|
|
895
|
+
const sev = l.severity === 'critical' ? '🔴' : l.severity === 'major' ? '🟡' : '';
|
|
896
|
+
lines.push(` ${emoji}${sev} \`${l.topic}\` — ${l.what_worked.slice(0, 100)}`);
|
|
897
|
+
}
|
|
898
|
+
lines.push('');
|
|
873
899
|
}
|
|
874
|
-
lines.push('');
|
|
875
900
|
}
|
|
876
901
|
else {
|
|
877
902
|
lines.push('📭 No lessons yet. Use `learn_from_attempts` after solving tasks.', '');
|
|
@@ -1328,21 +1353,33 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
1328
1353
|
catch { /* git not available or not a repo — silent skip */ }
|
|
1329
1354
|
}
|
|
1330
1355
|
const durationStr = durationMin !== undefined ? ` · ${durationMin} min` : '';
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
}
|
|
1345
|
-
|
|
1356
|
+
const totalAutoLearned = autoLearned.length + ambientLearned.length;
|
|
1357
|
+
// ── Shareable Session Summary Card ────────────────────────────────────────
|
|
1358
|
+
// Generated after each session so the user can share their progress.
|
|
1359
|
+
const tweetLines = [];
|
|
1360
|
+
if (durationMin !== undefined && durationMin > 0)
|
|
1361
|
+
tweetLines.push(`⏱ ${durationMin} min session`);
|
|
1362
|
+
if (totalAutoLearned > 0)
|
|
1363
|
+
tweetLines.push(`🧠 ${totalAutoLearned} lessons saved to Brain`);
|
|
1364
|
+
if (files_changed.length > 0)
|
|
1365
|
+
tweetLines.push(`📁 ${files_changed.length} file${files_changed.length > 1 ? 's' : ''} changed`);
|
|
1366
|
+
const tweetBody = tweetLines.length > 0
|
|
1367
|
+
? `${tweetLines.join(' · ')}\n\nMy AI Brain remembers this so I never repeat it. @cachlydev\ncachly.dev`
|
|
1368
|
+
: `Session saved to my AI Brain. No more re-explaining this tomorrow. @cachlydev\ncachly.dev`;
|
|
1369
|
+
const tweetURL = `https://twitter.com/intent/tweet?text=${encodeURIComponent(tweetBody)}`;
|
|
1370
|
+
const sessionCard = [
|
|
1371
|
+
``,
|
|
1372
|
+
`┌─────────────────────────────────────────────┐`,
|
|
1373
|
+
`│ 🧠 Session Summary Card │`,
|
|
1374
|
+
`│ Share your progress — cached forever │`,
|
|
1375
|
+
`├─────────────────────────────────────────────┤`,
|
|
1376
|
+
durationMin !== undefined ? `│ ⏱ Duration : ${String(durationMin + ' min').padEnd(26)}│` : '',
|
|
1377
|
+
totalAutoLearned > 0 ? `│ 📚 Learned : ${String(totalAutoLearned + ' lessons').padEnd(26)}│` : '',
|
|
1378
|
+
files_changed.length > 0 ? `│ 📁 Changed : ${String(files_changed.length + ' file' + (files_changed.length > 1 ? 's' : '')).padEnd(26)}│` : '',
|
|
1379
|
+
`├─────────────────────────────────────────────┤`,
|
|
1380
|
+
`│ 📣 Share: ${tweetURL.slice(0, 34).padEnd(34)}│`,
|
|
1381
|
+
`└─────────────────────────────────────────────┘`,
|
|
1382
|
+
].filter(l => l !== '').join('\n');
|
|
1346
1383
|
return [
|
|
1347
1384
|
`✅ **Session saved**${durationStr}`,
|
|
1348
1385
|
``,
|
|
@@ -1351,7 +1388,7 @@ export async function handleBrainTool(name, args, getConnection, apiFetch) {
|
|
|
1351
1388
|
lessons_learned !== undefined ? `🧠 **Lessons stored:** ${lessons_learned}` : '',
|
|
1352
1389
|
autoLearned.length > 0 ? `🤖 **Auto-learned:** ${autoLearned.length} lessons extracted from summary (${autoLearned.slice(0, 3).map(t => `\`${t}\``).join(', ')}${autoLearned.length > 3 ? '…' : ''})` : '',
|
|
1353
1390
|
ambientLearned.length > 0 ? `🌿 **Ambient git learning:** ${ambientLearned.length} commit${ambientLearned.length > 1 ? 's' : ''} auto-learned (${ambientLearned.slice(0, 3).map(t => `\`${t}\``).join(', ')}${ambientLearned.length > 3 ? '…' : ''})` : '',
|
|
1354
|
-
|
|
1391
|
+
sessionCard,
|
|
1355
1392
|
``,
|
|
1356
1393
|
`💡 Next session: \`session_start(focus="...")\` to see this summary.`,
|
|
1357
1394
|
].filter(l => l !== '').join('\n');
|