Skip to content

Commit 2c71027

Browse files
mirror: sync sdk/mcp @ 0.10.104 from monorepo
1 parent 3d96104 commit 2c71027

5 files changed

Lines changed: 194 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
88
---
99

10+
## [0.10.104] – 2026-06-06 — *"v4 Move 2 — Proactive briefing"*
11+
12+
### Added
13+
- **`brain_briefing`** — flips the Brain from pull-only to push. Call it on `file_open` (with the file path), `pr_open` (with the PR title/body), or `deploy` (with a short description) and the Brain proactively surfaces up to 5 ranked warnings — confidence, severity, what to watch, and a known fix — *before* anything breaks. Returns an overall `risk_level` (low/medium/high) derived from the strongest match. Optional `threshold` (default 0.6) tunes signal-to-noise.
14+
- New API endpoint `POST /api/v1/instances/:id/briefing` — tokenises the event context (camelCase-aware, same tokeniser as PR scan), matches it against the instance's lessons above the confidence threshold, and ranks results.
15+
16+
---
17+
1018
## [0.10.103] – 2026-06-05 — *"v4 Move 1 — Closed-loop CI"*
1119

1220
### Added

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@cachly-dev/mcp-server",
3-
"version": "0.10.103",
3+
"version": "0.10.104",
44
"mcpName": "io.github.cachly-dev/cachly-mcp",
55
"description": "Your AI forgets everything between sessions. cachly fixes that permanently. Persistent memory + causal root-cause analysis for Claude Code, Cursor, Copilot & Windsurf. Learns from every bug fix, git commit, and CI run automatically. Arrives pre-briefed. Predicts failures before they happen. One-command setup (npx ... autopilot). 121 MCP tools · Free tier forever · GDPR · EU servers.",
66
"type": "module",
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* brain_briefing — v4 Move 2: proactive briefing.
3+
*
4+
* Verifies:
5+
* - rejects invalid event_type
6+
* - rejects empty context
7+
* - shows "no risk" message when no warnings returned
8+
* - renders ranked warning table with risk level
9+
* - graceful fallback when API unreachable
10+
*/
11+
12+
import { describe, it, expect } from 'vitest';
13+
import { handleSyndicateTool } from '../handlers/syndicate.js';
14+
15+
const INSTANCE = 'test-instance';
16+
17+
function makeConn() {
18+
return (async (_id: string) => ({})) as unknown as Parameters<typeof handleSyndicateTool>[2];
19+
}
20+
21+
async function callTool(
22+
name: string,
23+
args: Record<string, unknown>,
24+
apiFetch: Parameters<typeof handleSyndicateTool>[3],
25+
) {
26+
return handleSyndicateTool(name, { instance_id: INSTANCE, ...args }, makeConn(), apiFetch);
27+
}
28+
29+
function mockFetch(response: unknown) {
30+
return (async () => response) as unknown as Parameters<typeof handleSyndicateTool>[3];
31+
}
32+
33+
function failFetch() {
34+
return (async () => { throw new Error('API down'); }) as unknown as Parameters<typeof handleSyndicateTool>[3];
35+
}
36+
37+
describe('brain_briefing', () => {
38+
it('rejects invalid event_type', async () => {
39+
await expect(callTool('brain_briefing', { event_type: 'on_save', context: 'auth.ts' }, mockFetch({})))
40+
.rejects.toThrow(/event_type must be/);
41+
});
42+
43+
it('rejects empty context', async () => {
44+
await expect(callTool('brain_briefing', { event_type: 'file_open', context: ' ' }, mockFetch({})))
45+
.rejects.toThrow(/context is required/);
46+
});
47+
48+
it('shows no-risk message when no warnings match', async () => {
49+
const fetch = mockFetch({ event_type: 'file_open', risk_level: 'low', warnings: [], matched_lessons: 3 });
50+
const result = await callTool('brain_briefing', { event_type: 'file_open', context: 'src/auth/login.ts' }, fetch);
51+
expect(result).toContain('no known risk patterns matched');
52+
expect(result).toContain('3 related lessons');
53+
});
54+
55+
it('renders ranked warning table with risk level', async () => {
56+
const fetch = mockFetch({
57+
event_type: 'pr_open',
58+
risk_level: 'high',
59+
matched_lessons: 4,
60+
warnings: [
61+
{ topic: 'auth:jwt', confidence: 0.85, severity: 'critical', message: 'JWT refresh race condition', fix: 'Lock token refresh with a mutex' },
62+
{ topic: 'deploy:k8s', confidence: 0.62, severity: 'major', message: 'Readiness probe too aggressive', fix: 'Increase failureThreshold to 10' },
63+
],
64+
});
65+
const result = await callTool('brain_briefing', { event_type: 'pr_open', context: 'Refactor auth + deploy pipeline' }, fetch);
66+
expect(result).toContain('risk: HIGH');
67+
expect(result).toContain('auth:jwt');
68+
expect(result).toContain('85%');
69+
expect(result).toContain('Lock token refresh with a mutex');
70+
});
71+
72+
it('passes threshold through to the API when provided', async () => {
73+
let capturedBody: string | undefined;
74+
const fetch = (async (_path: string, opts?: RequestInit) => {
75+
capturedBody = opts?.body as string;
76+
return { event_type: 'deploy', risk_level: 'low', warnings: [], matched_lessons: 0 };
77+
}) as unknown as Parameters<typeof handleSyndicateTool>[3];
78+
79+
await callTool('brain_briefing', { event_type: 'deploy', context: 'Deploy v2.4 to production', threshold: 0.8 }, fetch);
80+
expect(capturedBody).toBeDefined();
81+
expect(JSON.parse(capturedBody as string)).toMatchObject({ event_type: 'deploy', threshold: 0.8 });
82+
});
83+
84+
it('falls back gracefully when API is unreachable', async () => {
85+
const result = await callTool('brain_briefing', { event_type: 'file_open', context: 'src/db/migrate.go' }, failFetch());
86+
expect(result).toContain('Could not reach Brain API');
87+
});
88+
});

src/handlers/syndicate.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export const SYNDICATE_TOOL_NAMES = new Set([
1414
'brain_search', 'ckg_inspect', 'brain_predict', 'brain_plan',
1515
'brain_marketplace', 'brain_install',
1616
'brain_conflicts', 'brain_resolve_conflict',
17-
'brain_confirm_ci',
17+
'brain_confirm_ci', 'brain_briefing',
1818
]);
1919

2020
export async function handleSyndicateTool(
@@ -962,6 +962,72 @@ export async function handleSyndicateTool(
962962
return lines.join('\n');
963963
}
964964

965+
case 'brain_briefing': {
966+
const {
967+
instance_id,
968+
event_type,
969+
context,
970+
threshold,
971+
} = args as {
972+
instance_id: string;
973+
event_type: string;
974+
context: string;
975+
threshold?: number;
976+
};
977+
978+
if (!['file_open', 'pr_open', 'deploy', 'manual'].includes(event_type)) {
979+
throw new Error('event_type must be file_open, pr_open, deploy, or manual');
980+
}
981+
if (!context?.trim()) throw new Error('context is required');
982+
983+
type BriefingWarning = { topic: string; confidence: number; severity: string; message: string; fix: string };
984+
type BriefingResult = {
985+
event_type: string;
986+
risk_level: 'low' | 'medium' | 'high';
987+
warnings: BriefingWarning[];
988+
matched_lessons: number;
989+
};
990+
991+
let result: BriefingResult;
992+
try {
993+
result = await apiFetch<BriefingResult>(
994+
`/api/v1/instances/${instance_id}/briefing`,
995+
{
996+
method: 'POST',
997+
body: JSON.stringify({ event_type, context, ...(threshold !== undefined ? { threshold } : {}) }),
998+
},
999+
);
1000+
} catch {
1001+
return `⚠️ Could not reach Brain API — no proactive briefing available right now.`;
1002+
}
1003+
1004+
const eventIcon = event_type === 'file_open' ? '📂' : event_type === 'pr_open' ? '🔀' : event_type === 'deploy' ? '🚀' : '🔍';
1005+
1006+
if (result.warnings.length === 0) {
1007+
return [
1008+
`${eventIcon} **brain_briefing** — ${event_type} · ✅ no known risk patterns matched`,
1009+
'',
1010+
`Checked against ${result.matched_lessons} related lesson${result.matched_lessons !== 1 ? 's' : ''}. You're clear to proceed.`,
1011+
].join('\n');
1012+
}
1013+
1014+
const riskIcon = result.risk_level === 'high' ? '🔴' : result.risk_level === 'medium' ? '🟡' : '🟢';
1015+
const lines = [
1016+
`${eventIcon} **brain_briefing** — ${event_type} · ${riskIcon} risk: ${result.risk_level.toUpperCase()}`,
1017+
'',
1018+
`The Brain proactively found ${result.warnings.length} known pattern${result.warnings.length !== 1 ? 's' : ''} that may apply here:`,
1019+
'',
1020+
`| Topic | Confidence | Severity | What to watch | Known fix |`,
1021+
`|---|---|---|---|---|`,
1022+
];
1023+
for (const w of result.warnings) {
1024+
const fix = w.fix ? (w.fix.length > 80 ? w.fix.slice(0, 80) + '…' : w.fix) : '—';
1025+
lines.push(`| \`${w.topic}\` | ${(w.confidence * 100).toFixed(0)}% | ${w.severity} | ${w.message} | ${fix} |`);
1026+
}
1027+
lines.push('', `_Surfaced proactively — no \`smart_recall\` needed. Address the highest-confidence items first._`);
1028+
return lines.join('\n');
1029+
}
1030+
9651031
// ── Layer 3: MADC ─────────────────────────────────────────────────────────
9661032

9671033
default:

src/tools.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1971,6 +1971,36 @@ const TOOLS = [
19711971
required: ['instance_id', 'job_status', 'topics'],
19721972
},
19731973
},
1974+
// ── v4 Move 2: Proactive briefing ─────────────────────────────────────────
1975+
{
1976+
name: 'brain_briefing',
1977+
description:
1978+
'Push-based Brain warning: instead of waiting for you to ask, the Brain proactively checks ' +
1979+
'whether the file you just opened, the PR you are about to raise, or the deploy you are about ' +
1980+
'to run matches any known failure pattern — and surfaces warnings BEFORE something breaks. ' +
1981+
'Call this on file_open (with the file path as context), pr_open (with the PR title/body), ' +
1982+
'or deploy (with a short description of what is being deployed). ' +
1983+
'Returns a risk_level (low/medium/high) plus up to 5 ranked warnings with confidence and a known fix.',
1984+
inputSchema: {
1985+
type: 'object',
1986+
properties: {
1987+
instance_id: { type: 'string', description: 'Brain instance ID' },
1988+
event_type: {
1989+
type: 'string', enum: ['file_open', 'pr_open', 'deploy', 'manual'],
1990+
description: 'What triggered this briefing — determines how the context is interpreted',
1991+
},
1992+
context: {
1993+
type: 'string',
1994+
description: 'The event context: file path for file_open, title+body for pr_open, short description for deploy/manual',
1995+
},
1996+
threshold: {
1997+
type: 'number',
1998+
description: 'Minimum confidence (0–1) for a warning to be surfaced. Default 0.6 — raise it to reduce noise.',
1999+
},
2000+
},
2001+
required: ['instance_id', 'event_type', 'context'],
2002+
},
2003+
},
19742004
// ── Move 5: Privacy-preserving federation ────────────────────────────────
19752005
{
19762006
name: 'brain_contribute_signal',

0 commit comments

Comments
 (0)