@cachly-dev/mcp-server 0.10.129 → 0.10.130
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 +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -225
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -260
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -113
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4397
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1865
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -141
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -249
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -140
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -264
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -203
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -156
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -159
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/feldreparatur.d.ts +0 -58
- package/dist/src/feldreparatur.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/interferenz.d.ts +0 -111
- package/dist/src/interferenz.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -205
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/resonanz.d.ts +0 -85
- package/dist/src/resonanz.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -187
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -148
- package/dist/src/wertbeitrag.js.map +0 -1
|
@@ -1,904 +1,38 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
.map((x) => ({ topic: x.topic, what_worked: x.what_worked.slice(0, 160) }));
|
|
40
|
-
}
|
|
41
|
-
catch {
|
|
42
|
-
return []; // commons unavailable → org prediction stands alone (no regression).
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
async function renderOrgPredict(ctx, res, scope, apiFetch) {
|
|
46
|
-
const lines = [`🔮 **Org Brain Predict: "${ctx}"**`, ''];
|
|
47
|
-
for (const w of res.warnings) {
|
|
48
|
-
const otherTeams = Math.max(0, w.team_span - 1);
|
|
49
|
-
const totalHits = w.recurrence.reduce((s, r) => s + (r.count ?? 0), 0);
|
|
50
|
-
const sevIcon = w.severity === 'critical' ? '🔴' : w.severity === 'major' ? '🟠' : '🟡';
|
|
51
|
-
if (w.team_span >= 2) {
|
|
52
|
-
lines.push(`⚠️ **CROSS-TEAM RISK** — this pattern failed ${totalHits}× across ${otherTeams} other team${otherTeams === 1 ? '' : 's'}:`);
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
lines.push(`⚠️ **RISK** — this pattern failed ${totalHits}× before:`);
|
|
56
|
-
}
|
|
57
|
-
lines.push(` ${sevIcon} problem: \`${w.problem}\``);
|
|
58
|
-
for (const r of w.recurrence.slice(0, 6)) {
|
|
59
|
-
const last = r.last_seen ? ` (last: ${r.last_seen.slice(0, 10)})` : '';
|
|
60
|
-
lines.push(` • ${r.team} ×${r.count}${last}`);
|
|
61
|
-
}
|
|
62
|
-
const bestFix = w.fixes[0];
|
|
63
|
-
if (bestFix) {
|
|
64
|
-
const confPct = Math.round((bestFix.confidence ?? 0) * 100);
|
|
65
|
-
const confirmedBy = bestFix.source_team ? ` (${bestFix.source_team})` : '';
|
|
66
|
-
lines.push(` ✅ Proven fix — ${confPct}% confidence, confirmed ${bestFix.confirm_count}×${confirmedBy}:`);
|
|
67
|
-
lines.push(` ${bestFix.summary}`);
|
|
68
|
-
}
|
|
69
|
-
lines.push('');
|
|
70
|
-
}
|
|
71
|
-
if (scope === 'org+commons') {
|
|
72
|
-
// Overlay public Knowledge-Commons hits beneath the higher-signal org graph.
|
|
73
|
-
const commons = await fetchCommonsOverlay(ctx, res, apiFetch);
|
|
74
|
-
if (commons.length > 0) {
|
|
75
|
-
lines.push('🌐 **From the public Knowledge Commons** (cross-org, lower-signal):');
|
|
76
|
-
for (const c of commons) {
|
|
77
|
-
lines.push(` • \`${c.topic}\` — ${c.what_worked}`);
|
|
78
|
-
}
|
|
79
|
-
lines.push('');
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
lines.push(`💡 Heeded the warning? \`learn_from_attempts(..., tags=["prevented-by-org-graph"])\` → counts as a prevented cross-team mistake.`);
|
|
83
|
-
return lines.join('\n');
|
|
84
|
-
}
|
|
85
|
-
export const SYNDICATE_TOOL_NAMES = new Set([
|
|
86
|
-
'syndicate', 'syndicate_search', 'syndicate_stats', 'syndicate_trending',
|
|
87
|
-
'brain_search', 'ckg_inspect', 'brain_predict', 'brain_plan',
|
|
88
|
-
'brain_marketplace', 'brain_install',
|
|
89
|
-
'brain_conflicts', 'brain_resolve_conflict',
|
|
90
|
-
'brain_confirm_ci', 'brain_briefing',
|
|
91
|
-
]);
|
|
92
|
-
export async function handleSyndicateTool(name, args, getConnection, apiFetch) {
|
|
93
|
-
switch (name) {
|
|
94
|
-
case 'syndicate': {
|
|
95
|
-
const { topic, outcome = 'success', what_worked, what_failed = '', severity = 'minor', tags = [], scope = 'public' } = args;
|
|
96
|
-
if (!topic || !what_worked) {
|
|
97
|
-
throw new McpError(ErrorCode.InvalidParams, 'topic and what_worked are required');
|
|
98
|
-
}
|
|
99
|
-
const body = { topic, outcome, what_worked, what_failed, severity, tags, scope };
|
|
100
|
-
const res = await apiFetch('/api/v1/syndication/contribute', { method: 'POST', body: JSON.stringify(body) });
|
|
101
|
-
const scopeLabel = scope === 'org' ? '🏢 org-private' : '🌐 global commons';
|
|
102
|
-
const dedupNote = res.deduped
|
|
103
|
-
? `\n> ♻️ Duplicate detected — trust score incremented for the existing lesson.`
|
|
104
|
-
: '';
|
|
105
|
-
return [
|
|
106
|
-
`${scope === 'org' ? '🏢' : '🌐'} **Lesson syndicated to the ${scope === 'org' ? 'org Knowledge Commons' : 'global Knowledge Commons'}**${dedupNote}`,
|
|
107
|
-
``,
|
|
108
|
-
`**ID:** \`${res.id}\``,
|
|
109
|
-
`**Topic:** \`${res.topic}\` · **Outcome:** ${res.outcome} · **Scope:** ${scopeLabel}`,
|
|
110
|
-
``,
|
|
111
|
-
scope === 'org'
|
|
112
|
-
? `This lesson is visible only within your organisation. Use \`syndicate_search(scope="org")\` to find it.`
|
|
113
|
-
: `Your lesson is now searchable by every AI brain in the network.`,
|
|
114
|
-
`When another instance confirms it works, its trust score rises — and so does your contributor reputation.`,
|
|
115
|
-
``,
|
|
116
|
-
`**Tip:** Use \`syndicate_search(q="${topic}")\` to see all community lessons on this topic.`,
|
|
117
|
-
].join('\n');
|
|
118
|
-
}
|
|
119
|
-
// ── v0.7 Knowledge Syndication: syndicate_search ─────────────────────────
|
|
120
|
-
case 'syndicate_search': {
|
|
121
|
-
const { q = '', limit = 20, category = '', scope = '' } = args;
|
|
122
|
-
const params = new URLSearchParams();
|
|
123
|
-
if (q)
|
|
124
|
-
params.set('q', q);
|
|
125
|
-
if (category)
|
|
126
|
-
params.set('category', category);
|
|
127
|
-
if (scope)
|
|
128
|
-
params.set('scope', scope);
|
|
129
|
-
params.set('limit', String(Math.min(Math.max(1, limit), 50)));
|
|
130
|
-
const res = await apiFetch(`/api/v1/syndication/search?${params}`);
|
|
131
|
-
if (!res.results || res.results.length === 0) {
|
|
132
|
-
return q
|
|
133
|
-
? `No lessons found for "${q}" in the global Knowledge Commons yet.\n\nBe the first to contribute: \`syndicate(topic="...", what_worked="...")\``
|
|
134
|
-
: `The global Knowledge Commons is empty. Be the first contributor:\n\`syndicate(topic="deploy:api", what_worked="...")\``;
|
|
135
|
-
}
|
|
136
|
-
const outcomeIcon = (o) => o === 'success' ? '✅' : o === 'failure' ? '❌' : '⚠️';
|
|
137
|
-
const severityLabel = (s) => s === 'critical' ? '🔴' : s === 'major' ? '🟡' : '🟢';
|
|
138
|
-
const confirmBar = (n) => {
|
|
139
|
-
const filled = Math.min(10, Math.round(n / 5));
|
|
140
|
-
return '█'.repeat(filled) + '░'.repeat(10 - filled) + ` ×${n}`;
|
|
141
|
-
};
|
|
142
|
-
const header = [q, category].filter(Boolean).join(' · ');
|
|
143
|
-
const lines = [
|
|
144
|
-
`## 🌐 Global Knowledge Commons${header ? ` — ${header}` : ' — Recent'}`,
|
|
145
|
-
`*${res.count} lesson${res.count === 1 ? '' : 's'} found*`,
|
|
146
|
-
``,
|
|
147
|
-
];
|
|
148
|
-
for (const lesson of res.results) {
|
|
149
|
-
lines.push(`### ${outcomeIcon(lesson.outcome)} \`${lesson.topic}\` ${severityLabel(lesson.severity)}`, `**Trust:** ${confirmBar(lesson.confirm_count)}`, lesson.what_worked ? `**What worked:** ${lesson.what_worked}` : '', lesson.what_failed ? `**What failed:** ${lesson.what_failed}` : '', `*Contributed ${new Date(lesson.created_at).toLocaleDateString('de-DE')} · ID: \`${lesson.id}\`*`, ``);
|
|
150
|
-
}
|
|
151
|
-
lines.push(`---`, `**Confirm** (this helped you): \`syndicate(topic="${res.results[0]?.topic ?? '...'}", what_worked="...")\` → auto-deduped, trust +1`, `**Contribute your own:** \`syndicate(topic="fix:...", what_worked="...")\``, `**Filter by category:** \`syndicate_search(category="fix")\``);
|
|
152
|
-
return lines.filter(l => l !== '').join('\n');
|
|
153
|
-
}
|
|
154
|
-
// ── v0.7 Knowledge Syndication: syndicate_stats ──────────────────────────
|
|
155
|
-
case 'syndicate_stats': {
|
|
156
|
-
const res = await apiFetch('/api/v1/syndication/stats');
|
|
157
|
-
const confirmBar = (n) => {
|
|
158
|
-
const filled = Math.min(10, Math.round(n / 5));
|
|
159
|
-
return '█'.repeat(filled) + '░'.repeat(10 - filled) + ` ×${n}`;
|
|
160
|
-
};
|
|
161
|
-
const lines = [
|
|
162
|
-
`## 🌐 Global Knowledge Commons — Stats`,
|
|
163
|
-
``,
|
|
164
|
-
`| Metric | Value |`,
|
|
165
|
-
`|---|---|`,
|
|
166
|
-
`| Total lessons | **${res.total_lessons.toLocaleString()}** |`,
|
|
167
|
-
`| Total confirms | **${res.total_confirms.toLocaleString()}** |`,
|
|
168
|
-
`| Added last 7 days | **${res.added_last_7_days}** |`,
|
|
169
|
-
``,
|
|
170
|
-
`### Top Categories`,
|
|
171
|
-
];
|
|
172
|
-
for (const cat of res.top_categories ?? []) {
|
|
173
|
-
lines.push(`- \`${cat.category}\` — ${cat.count} lesson${cat.count === 1 ? '' : 's'}`);
|
|
174
|
-
}
|
|
175
|
-
lines.push(``, `### Most Trusted Lessons`);
|
|
176
|
-
for (const lesson of res.most_trusted ?? []) {
|
|
177
|
-
lines.push(`**\`${lesson.topic}\`** ${confirmBar(lesson.confirm_count)}`, `> ${lesson.what_worked.slice(0, 120)}${lesson.what_worked.length > 120 ? '…' : ''}`, ``);
|
|
178
|
-
}
|
|
179
|
-
lines.push(`---`, `**Contribute:** \`syndicate(topic="...", what_worked="...")\` | **Search:** \`syndicate_search(q="your problem")\``);
|
|
180
|
-
// Top contributors (anonymous scores)
|
|
181
|
-
if (res.top_contributors?.length) {
|
|
182
|
-
lines.push(``, `### 🏅 Top Contributors (anonymous)`);
|
|
183
|
-
for (const c of res.top_contributors) {
|
|
184
|
-
lines.push(`- Trust **${c.trust_score}** · ${c.lessons_count} lesson${c.lessons_count === 1 ? '' : 's'} · ${c.confirms_received} confirms received`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return lines.join('\n');
|
|
188
|
-
}
|
|
189
|
-
// ── v0.8 Knowledge Syndication: syndicate_trending ───────────────────────
|
|
190
|
-
case 'syndicate_trending': {
|
|
191
|
-
const { limit = 10 } = args;
|
|
192
|
-
const params = new URLSearchParams({ limit: String(Math.min(Math.max(1, limit), 50)) });
|
|
193
|
-
const res = await apiFetch(`/api/v1/syndication/trending?${params}`);
|
|
194
|
-
if (!res.results || res.results.length === 0) {
|
|
195
|
-
return [
|
|
196
|
-
`## 📈 Trending in the Knowledge Commons`,
|
|
197
|
-
``,
|
|
198
|
-
`No trending lessons yet (need at least 2 confirms in the last 7 days).`,
|
|
199
|
-
``,
|
|
200
|
-
`Contribute and confirm lessons to see them trend: \`syndicate(topic="...", what_worked="...")\``,
|
|
201
|
-
].join('\n');
|
|
202
|
-
}
|
|
203
|
-
const outcomeIcon = (o) => o === 'success' ? '✅' : o === 'failure' ? '❌' : '⚠️';
|
|
204
|
-
const severityLabel = (s) => s === 'critical' ? '🔴' : s === 'major' ? '🟡' : '🟢';
|
|
205
|
-
const confirmBar = (n) => {
|
|
206
|
-
const filled = Math.min(10, Math.round(n / 5));
|
|
207
|
-
return '█'.repeat(filled) + '░'.repeat(10 - filled) + ` ×${n}`;
|
|
208
|
-
};
|
|
209
|
-
const trendBar = (score) => {
|
|
210
|
-
const filled = Math.min(10, Math.round(score * 2));
|
|
211
|
-
return '▲'.repeat(filled) + '△'.repeat(10 - filled) + ` ${score.toFixed(2)}/day`;
|
|
212
|
-
};
|
|
213
|
-
const lines = [
|
|
214
|
-
`## 📈 Trending in the Knowledge Commons`,
|
|
215
|
-
`*Lessons with the fastest confirmation velocity in the last 7 days*`,
|
|
216
|
-
``,
|
|
217
|
-
];
|
|
218
|
-
for (const lesson of res.results) {
|
|
219
|
-
lines.push(`### ${outcomeIcon(lesson.outcome)} \`${lesson.topic}\` ${severityLabel(lesson.severity)}`, `**Trend:** ${trendBar(lesson.trend_score)} | **Trust:** ${confirmBar(lesson.confirm_count)}`, lesson.what_worked ? `**What worked:** ${lesson.what_worked.slice(0, 200)}${lesson.what_worked.length > 200 ? '…' : ''}` : '', `*ID: \`${lesson.id}\` · ${new Date(lesson.created_at).toLocaleDateString('de-DE')}*`, ``);
|
|
220
|
-
}
|
|
221
|
-
lines.push(`---`, `**Confirm** (if this helped you): \`syndicate(topic="${res.results[0]?.topic ?? '...'}", what_worked="...")\` → auto-deduped, trust +1`, `**All trending:** \`syndicate_trending(limit=50)\` | **Search:** \`syndicate_search(q="...")\``);
|
|
222
|
-
return lines.filter(l => l !== '').join('\n');
|
|
223
|
-
}
|
|
224
|
-
// ── Layer 1: brain_search ─────────────────────────────────────────────────
|
|
225
|
-
case 'brain_search': {
|
|
226
|
-
const { instance_id, query, limit = 15 } = args;
|
|
227
|
-
const redis = await getConnection(instance_id);
|
|
228
|
-
// BM25+ over ALL brain key namespaces
|
|
229
|
-
const allMatches = await keywordSearch(redis, [
|
|
230
|
-
'cachly:lesson:best:*',
|
|
231
|
-
'cachly:ctx:*',
|
|
232
|
-
'cachly:idx:*',
|
|
233
|
-
'cachly:session:last',
|
|
234
|
-
'cachly:session:handoff',
|
|
235
|
-
'cachly:roadmap:*',
|
|
236
|
-
'cachly:ckg:node:*',
|
|
237
|
-
], query, limit);
|
|
238
|
-
if (allMatches.length === 0) {
|
|
239
|
-
return [`🔎 **Brain Search: "${query}"**`, '', `No results found across all brain data.`, '', `💡 Try \`smart_recall\` or check \`list_remembered\`.`].join('\n');
|
|
240
|
-
}
|
|
241
|
-
const lines = [`🔎 **Brain Search: "${query}"** — ${allMatches.length} result${allMatches.length !== 1 ? 's' : ''} across all brain data\n`];
|
|
242
|
-
for (const m of allMatches.slice(0, limit)) {
|
|
243
|
-
const ns = m.key.startsWith('cachly:lesson:') ? '💡 lesson'
|
|
244
|
-
: m.key.startsWith('cachly:ctx:') ? '📝 context'
|
|
245
|
-
: m.key.startsWith('cachly:idx:') ? '📂 index'
|
|
246
|
-
: m.key.startsWith('cachly:session:') ? '🕐 session'
|
|
247
|
-
: m.key.startsWith('cachly:roadmap:') ? '🗺️ roadmap'
|
|
248
|
-
: m.key.startsWith('cachly:ckg:node:') ? '🕸️ ckg-node'
|
|
249
|
-
: '🗄️ data';
|
|
250
|
-
const preview = m.content.slice(0, 280).replace(/\n/g, ' ');
|
|
251
|
-
lines.push(`**${ns}** \`${m.key.split(':').slice(2).join(':')}\` _(BM25: ${m.score.toFixed(2)})_`);
|
|
252
|
-
lines.push(`> ${preview}${m.content.length > 280 ? '…' : ''}\n`);
|
|
253
|
-
}
|
|
254
|
-
return lines.join('\n');
|
|
255
|
-
}
|
|
256
|
-
// ── Layer 1: ckg_inspect ─────────────────────────────────────────────────
|
|
257
|
-
case 'ckg_inspect': {
|
|
258
|
-
const { instance_id, concept, max_hops = 2 } = args;
|
|
259
|
-
const redis = await getConnection(instance_id);
|
|
260
|
-
const conceptId = ckgSlug(concept);
|
|
261
|
-
const visited = new Set();
|
|
262
|
-
const allEdges = [];
|
|
263
|
-
// BFS traversal of CKG
|
|
264
|
-
const queue = [{ id: conceptId, hop: 0 }];
|
|
265
|
-
while (queue.length > 0) {
|
|
266
|
-
const { id, hop } = queue.shift();
|
|
267
|
-
if (visited.has(id) || hop > max_hops)
|
|
268
|
-
continue;
|
|
269
|
-
visited.add(id);
|
|
270
|
-
const fromKeys = await redis.smembers(`cachly:ckg:idx:from:${id}`);
|
|
271
|
-
const toKeys = await redis.smembers(`cachly:ckg:idx:to:${id}`);
|
|
272
|
-
for (const ek of [...fromKeys, ...toKeys].slice(0, 50)) {
|
|
273
|
-
const raw = await redis.get(ek);
|
|
274
|
-
if (!raw)
|
|
275
|
-
continue;
|
|
276
|
-
const edge = safeJsonParse(raw, null);
|
|
277
|
-
if (!edge)
|
|
278
|
-
continue;
|
|
279
|
-
allEdges.push(edge);
|
|
280
|
-
if (hop < max_hops) {
|
|
281
|
-
if (!visited.has(edge.from))
|
|
282
|
-
queue.push({ id: edge.from, hop: hop + 1 });
|
|
283
|
-
if (!visited.has(edge.to))
|
|
284
|
-
queue.push({ id: edge.to, hop: hop + 1 });
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (allEdges.length === 0) {
|
|
289
|
-
// Try fuzzy: scan for nodes matching the concept as substring
|
|
290
|
-
const nodeKeys = [];
|
|
291
|
-
const nStream = redis.scanStream({ match: `cachly:ckg:node:*${conceptId}*`, count: 100 });
|
|
292
|
-
await new Promise((res, rej) => { nStream.on('data', (b) => nodeKeys.push(...b)); nStream.on('end', res); nStream.on('error', rej); });
|
|
293
|
-
if (nodeKeys.length === 0) {
|
|
294
|
-
return [`🕸️ **CKG Inspect: "${concept}"**`, '', `No CKG nodes found. The graph builds automatically as you call \`learn_from_attempts\`.`, '', `💡 Once you have lessons stored, CKG edges will appear here.`].join('\n');
|
|
295
|
-
}
|
|
296
|
-
const nodeList = nodeKeys.slice(0, 10).map(k => ` • \`${k.replace('cachly:ckg:node:', '')}\``).join('\n');
|
|
297
|
-
return [`🕸️ **CKG Inspect: "${concept}"**`, '', `No edges found for \`${conceptId}\`, but found similar nodes:`, nodeList, '', `Try: \`ckg_inspect(concept="<exact-node-id>")\``].join('\n');
|
|
298
|
-
}
|
|
299
|
-
// Sort by confidence desc, deduplicate
|
|
300
|
-
const edgeSeen = new Set();
|
|
301
|
-
const unique = allEdges.filter(e => {
|
|
302
|
-
const k = `${e.from}:${e.edgeType}:${e.to}`;
|
|
303
|
-
if (edgeSeen.has(k))
|
|
304
|
-
return false;
|
|
305
|
-
edgeSeen.add(k);
|
|
306
|
-
return true;
|
|
307
|
-
}).sort((a, b) => b.confidence - a.confidence);
|
|
308
|
-
const EDGE_ICON = { fixes: '🔧', requires: '🔗', 'co-occurs': '🔄', causes: '⚡', contradicts: '⚠️', degrades_under: '📉' };
|
|
309
|
-
const lines = [`🕸️ **CKG Inspect: "${concept}"** (${unique.length} edge${unique.length !== 1 ? 's' : ''}, ${visited.size} node${visited.size !== 1 ? 's' : ''} traversed)\n`];
|
|
310
|
-
// Group by edge type
|
|
311
|
-
const byType = new Map();
|
|
312
|
-
for (const e of unique) {
|
|
313
|
-
if (!byType.has(e.edgeType))
|
|
314
|
-
byType.set(e.edgeType, []);
|
|
315
|
-
byType.get(e.edgeType).push(e);
|
|
316
|
-
}
|
|
317
|
-
for (const [eType, edges] of byType) {
|
|
318
|
-
const icon = EDGE_ICON[eType] ?? '→';
|
|
319
|
-
lines.push(`**${icon} ${eType}** (${edges.length})`);
|
|
320
|
-
for (const e of edges.slice(0, 8)) {
|
|
321
|
-
const confPct = Math.round(e.confidence * 100);
|
|
322
|
-
const bar = '▓'.repeat(Math.round(confPct / 10)) + '░'.repeat(10 - Math.round(confPct / 10));
|
|
323
|
-
lines.push(` \`${e.from}\` → \`${e.to}\` ${bar} ${confPct}% (${e.successes.toFixed(1)}/${e.trials} trials)`);
|
|
324
|
-
}
|
|
325
|
-
lines.push('');
|
|
326
|
-
}
|
|
327
|
-
lines.push(`💡 Expand: \`ckg_inspect(concept="${concept}", max_hops=3)\` | Predict: \`brain_predict(context="${concept}")\``);
|
|
328
|
-
return lines.join('\n');
|
|
329
|
-
}
|
|
330
|
-
// ── Layer 4: brain_predict (PPE) ─────────────────────────────────────────
|
|
331
|
-
case 'brain_predict': {
|
|
332
|
-
const { instance_id, context: ctx, top_k = 5, scope = 'instance' } = args;
|
|
333
|
-
// scope="org" / "org+commons" — widen prediction across the whole org via
|
|
334
|
-
// the Org Knowledge Graph (§3.4). Resolve the org from the instance the same
|
|
335
|
-
// way cache_org_stats does (instance record → org_id), then call the
|
|
336
|
-
// server-side traversal. Graceful-degrade to the instance path on any miss
|
|
337
|
-
// (no org, endpoint unavailable, empty graph) so there is zero regression.
|
|
338
|
-
if (scope === 'org' || scope === 'org+commons') {
|
|
339
|
-
const orgResult = await tryOrgPredict(instance_id, ctx, top_k, scope, apiFetch);
|
|
340
|
-
if (orgResult !== null)
|
|
341
|
-
return orgResult;
|
|
342
|
-
// fall through to the instance-scoped path below.
|
|
343
|
-
}
|
|
344
|
-
const redis = await getConnection(instance_id);
|
|
345
|
-
const ctxTokens = ctx.toLowerCase().replace(/[^a-z0-9\s\-_:]/g, ' ').split(/\s+/).filter(t => t.length > 2);
|
|
346
|
-
const predictions = [];
|
|
347
|
-
for (const token of ctxTokens.slice(0, 6)) {
|
|
348
|
-
const nodeKeys = [];
|
|
349
|
-
const nStream = redis.scanStream({ match: `cachly:ckg:node:*${token}*`, count: 50 });
|
|
350
|
-
await new Promise((res, rej) => { nStream.on('data', (b) => nodeKeys.push(...b)); nStream.on('end', res); nStream.on('error', rej); });
|
|
351
|
-
for (const nk of nodeKeys.slice(0, 5)) {
|
|
352
|
-
const nodeRaw = await redis.get(nk);
|
|
353
|
-
if (!nodeRaw)
|
|
354
|
-
continue;
|
|
355
|
-
const node = safeJsonParse(nodeRaw, null);
|
|
356
|
-
if (!node)
|
|
357
|
-
continue;
|
|
358
|
-
const edgeKeys = await redis.smembers(`cachly:ckg:idx:from:${node.id}`);
|
|
359
|
-
for (const ek of edgeKeys.slice(0, 20)) {
|
|
360
|
-
const edgeRaw = await redis.get(ek);
|
|
361
|
-
if (!edgeRaw)
|
|
362
|
-
continue;
|
|
363
|
-
const edge = safeJsonParse(edgeRaw, null);
|
|
364
|
-
if (!edge)
|
|
365
|
-
continue;
|
|
366
|
-
// Only interested in fixes and co-occurs for prediction
|
|
367
|
-
if (edge.edgeType !== 'fixes' && edge.edgeType !== 'co-occurs' && edge.edgeType !== 'causes')
|
|
368
|
-
continue;
|
|
369
|
-
const lessonRaw = await redis.get(`cachly:lesson:best:${edge.from}`);
|
|
370
|
-
const lesson = lessonRaw ? safeJsonParse(lessonRaw, null) ?? undefined : undefined;
|
|
371
|
-
predictions.push({ concept: node.id, edgeType: edge.edgeType, target: edge.to, confidence: edge.confidence, lesson });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// Step 2: Text-based fallback — scan lessons for matching topics
|
|
376
|
-
const textPredictions = [];
|
|
377
|
-
const lessonKeys = [];
|
|
378
|
-
const lStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
|
|
379
|
-
await new Promise((res, rej) => { lStream.on('data', (b) => lessonKeys.push(...b)); lStream.on('end', res); lStream.on('error', rej); });
|
|
380
|
-
for (const k of lessonKeys) {
|
|
381
|
-
const raw = await redis.get(k);
|
|
382
|
-
if (!raw)
|
|
383
|
-
continue;
|
|
384
|
-
{
|
|
385
|
-
const l = safeJsonParse(raw, null);
|
|
386
|
-
if (!l)
|
|
387
|
-
continue;
|
|
388
|
-
const haystack = [l.topic, l.what_failed ?? '', l.what_worked ?? ''].join(' ').toLowerCase();
|
|
389
|
-
const score = ctxTokens.reduce((s, t) => s + (haystack.includes(t) ? 1 : 0), 0);
|
|
390
|
-
if (score >= 1 && l.outcome !== 'failure') {
|
|
391
|
-
const conf = calculateConfidence(l);
|
|
392
|
-
textPredictions.push({ ...l, confidence: conf });
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
textPredictions.sort((a, b) => b.confidence - a.confidence);
|
|
397
|
-
if (predictions.length === 0 && textPredictions.length === 0) {
|
|
398
|
-
return [
|
|
399
|
-
`🔮 **Brain Predict: "${ctx}"**`,
|
|
400
|
-
``,
|
|
401
|
-
`No predictions yet — the brain hasn't seen this domain.`,
|
|
402
|
-
``,
|
|
403
|
-
`💡 As you solve problems in this area and call \`learn_from_attempts\`, the CKG builds up and predictions become available.`,
|
|
404
|
-
].join('\n');
|
|
405
|
-
}
|
|
406
|
-
const lines = [`🔮 **Brain Predict: "${ctx}"**\n`];
|
|
407
|
-
// CKG-based predictions
|
|
408
|
-
if (predictions.length > 0) {
|
|
409
|
-
const pSeen = new Set();
|
|
410
|
-
const pUniq = predictions.filter(p => { const k = `${p.concept}:${p.edgeType}:${p.target}`; if (pSeen.has(k))
|
|
411
|
-
return false; pSeen.add(k); return true; })
|
|
412
|
-
.sort((a, b) => b.confidence - a.confidence).slice(0, top_k);
|
|
413
|
-
lines.push(`### 🕸️ CKG Predictions (based on ${pUniq.length} known edges)`);
|
|
414
|
-
for (const p of pUniq) {
|
|
415
|
-
const confPct = Math.round(p.confidence * 100);
|
|
416
|
-
const icon = p.edgeType === 'fixes' ? '🔧' : p.edgeType === 'co-occurs' ? '🔄' : '⚡';
|
|
417
|
-
lines.push(`${icon} **${confPct}%** \`${p.concept}\` _${p.edgeType}_ \`${p.target}\``);
|
|
418
|
-
if (p.lesson?.what_worked)
|
|
419
|
-
lines.push(` ✅ ${p.lesson.what_worked.slice(0, 120)}`);
|
|
420
|
-
}
|
|
421
|
-
lines.push('');
|
|
422
|
-
}
|
|
423
|
-
// Text-based lesson predictions
|
|
424
|
-
if (textPredictions.length > 0) {
|
|
425
|
-
lines.push(`### 📚 Relevant Lessons (${Math.min(textPredictions.length, top_k)} pre-loaded)`);
|
|
426
|
-
for (const l of textPredictions.slice(0, top_k)) {
|
|
427
|
-
const confPct = Math.round(l.confidence * 100);
|
|
428
|
-
lines.push(` ✅ **${confPct}%** \`${l.topic}\` — ${(l.what_worked ?? '').slice(0, 120)}`);
|
|
429
|
-
}
|
|
430
|
-
lines.push('');
|
|
431
|
-
}
|
|
432
|
-
lines.push(`💡 Outcome confirmed? \`learn_from_attempts(topic="fix:...", outcome="success", ...)\` → improves future predictions`);
|
|
433
|
-
return lines.join('\n');
|
|
434
|
-
}
|
|
435
|
-
// ── brain_plan: generative planning on top of the CKG ───────────────────────
|
|
436
|
-
// Where brain_predict answers "what might fail?", brain_plan answers "what
|
|
437
|
-
// should I do, in what order?". It reads the same lessons + CKG but reframes
|
|
438
|
-
// them as an actionable plan: ranked failure modes to avoid, ordered steps
|
|
439
|
-
// grounded in proven fixes (dependency-aware), and a pre-flight checklist.
|
|
440
|
-
case 'brain_plan': {
|
|
441
|
-
const { instance_id, task, top_k = 5 } = args;
|
|
442
|
-
if (!task || !task.trim()) {
|
|
443
|
-
throw new McpError(ErrorCode.InvalidParams, 'task is required');
|
|
444
|
-
}
|
|
445
|
-
const redis = await getConnection(instance_id);
|
|
446
|
-
const taskTokens = task.toLowerCase().replace(/[^a-z0-9\s\-_:]/g, ' ').split(/\s+/).filter(t => t.length > 2);
|
|
447
|
-
// Scan every best-lesson once and score by token overlap against the task.
|
|
448
|
-
const lessonKeys = [];
|
|
449
|
-
const lStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 200 });
|
|
450
|
-
await new Promise((res, rej) => { lStream.on('data', (b) => lessonKeys.push(...b)); lStream.on('end', res); lStream.on('error', rej); });
|
|
451
|
-
const scored = [];
|
|
452
|
-
for (const k of lessonKeys) {
|
|
453
|
-
const raw = await redis.get(k);
|
|
454
|
-
if (!raw)
|
|
455
|
-
continue;
|
|
456
|
-
const l = safeJsonParse(raw, null);
|
|
457
|
-
if (!l)
|
|
458
|
-
continue;
|
|
459
|
-
const haystack = [l.topic, l.what_worked ?? '', l.what_failed ?? ''].join(' ').toLowerCase();
|
|
460
|
-
const score = taskTokens.reduce((s, t) => s + (haystack.includes(t) ? 1 : 0), 0);
|
|
461
|
-
if (score >= 1)
|
|
462
|
-
scored.push({ l, score, confidence: calculateConfidence(l) });
|
|
463
|
-
}
|
|
464
|
-
if (scored.length === 0) {
|
|
465
|
-
return [
|
|
466
|
-
`🗺️ **Brain Plan: "${task}"**`,
|
|
467
|
-
``,
|
|
468
|
-
`No grounded plan yet — the brain hasn't seen this area.`,
|
|
469
|
-
``,
|
|
470
|
-
`💡 As you work here and call \`learn_from_attempts\`, brain_plan will assemble`,
|
|
471
|
-
` an ordered, proven plan from your own history. For now, proceed carefully`,
|
|
472
|
-
` and capture what works.`,
|
|
473
|
-
].join('\n');
|
|
474
|
-
}
|
|
475
|
-
// Rank: relevance first, then confidence. Split into failure modes (things
|
|
476
|
-
// that bit us before) vs. proven steps (successful fixes).
|
|
477
|
-
scored.sort((a, b) => (b.score - a.score) || (b.confidence - a.confidence));
|
|
478
|
-
const failureModes = scored.filter(s => s.l.outcome === 'failure' || (s.l.what_failed ?? '').trim().length > 0);
|
|
479
|
-
const provenSteps = scored.filter(s => s.l.outcome === 'success' || s.l.outcome === 'partial');
|
|
480
|
-
const lines = [`🗺️ **Brain Plan: "${task}"**`, '', `_Grounded in ${scored.length} relevant lesson${scored.length !== 1 ? 's' : ''} from your own Brain._`, ''];
|
|
481
|
-
// ── Section 1: failure modes to avoid, ranked by confidence ───────────────
|
|
482
|
-
if (failureModes.length > 0) {
|
|
483
|
-
lines.push(`### ⚠️ Likely failure modes (avoid these)`);
|
|
484
|
-
const sevRank = { critical: 0, major: 1, minor: 2 };
|
|
485
|
-
failureModes
|
|
486
|
-
.slice()
|
|
487
|
-
.sort((a, b) => (sevRank[a.l.severity ?? 'minor'] ?? 1) - (sevRank[b.l.severity ?? 'minor'] ?? 1) || b.confidence - a.confidence)
|
|
488
|
-
.slice(0, top_k)
|
|
489
|
-
.forEach(({ l, confidence }) => {
|
|
490
|
-
const sevEmoji = l.severity === 'critical' ? '🔴' : l.severity === 'major' ? '🟡' : '🟢';
|
|
491
|
-
const detail = (l.what_failed ?? l.what_worked ?? '').slice(0, 120);
|
|
492
|
-
lines.push(`${sevEmoji} **${Math.round(confidence * 100)}%** \`${l.topic}\`${detail ? ` — ${detail}` : ''}`);
|
|
493
|
-
});
|
|
494
|
-
lines.push('');
|
|
495
|
-
}
|
|
496
|
-
// ── Section 2: ordered, proven steps (dependency-aware) ───────────────────
|
|
497
|
-
if (provenSteps.length > 0) {
|
|
498
|
-
// Order steps so that any step a lesson depends_on comes first. We only
|
|
499
|
-
// have topics + depends_on links; do a light topological pass over the
|
|
500
|
-
// selected set, falling back to relevance order for anything unresolved.
|
|
501
|
-
const selected = provenSteps.slice(0, top_k);
|
|
502
|
-
const byTopic = new Map(selected.map(s => [s.l.topic, s]));
|
|
503
|
-
const ordered = [];
|
|
504
|
-
const placed = new Set();
|
|
505
|
-
const place = (s, depth) => {
|
|
506
|
-
if (placed.has(s.l.topic) || depth > 10)
|
|
507
|
-
return;
|
|
508
|
-
for (const dep of s.l.depends_on ?? []) {
|
|
509
|
-
const depEntry = byTopic.get(dep);
|
|
510
|
-
if (depEntry && !placed.has(dep))
|
|
511
|
-
place(depEntry, depth + 1);
|
|
512
|
-
}
|
|
513
|
-
if (!placed.has(s.l.topic)) {
|
|
514
|
-
placed.add(s.l.topic);
|
|
515
|
-
ordered.push(s);
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
for (const s of selected)
|
|
519
|
-
place(s, 0);
|
|
520
|
-
lines.push(`### 🔧 Recommended steps (proven fixes, in order)`);
|
|
521
|
-
ordered.forEach(({ l, confidence }, i) => {
|
|
522
|
-
lines.push(`${i + 1}. **${Math.round(confidence * 100)}%** \`${l.topic}\` — ${(l.what_worked ?? '').slice(0, 140)}`);
|
|
523
|
-
const cmds = (l.commands ?? []).slice(0, 2);
|
|
524
|
-
if (cmds.length > 0)
|
|
525
|
-
lines.push(` \`\`\`\n ${cmds.join('\n ')}\n \`\`\``);
|
|
526
|
-
if ((l.depends_on ?? []).length > 0)
|
|
527
|
-
lines.push(` ↳ depends on: ${(l.depends_on ?? []).map(d => `\`${d}\``).join(', ')}`);
|
|
528
|
-
});
|
|
529
|
-
lines.push('');
|
|
530
|
-
}
|
|
531
|
-
// ── Section 3: pre-flight checklist ──────────────────────────────────────
|
|
532
|
-
const checklist = scored
|
|
533
|
-
.filter(s => (s.l.file_paths ?? []).length > 0 || (s.l.commands ?? []).length > 0)
|
|
534
|
-
.slice(0, 4);
|
|
535
|
-
if (checklist.length > 0) {
|
|
536
|
-
lines.push(`### ✅ Pre-flight checklist`);
|
|
537
|
-
const touchedFiles = [...new Set(checklist.flatMap(s => s.l.file_paths ?? []))].slice(0, 6);
|
|
538
|
-
if (touchedFiles.length > 0) {
|
|
539
|
-
lines.push(` • Review files that were involved before: ${touchedFiles.map(f => `\`${f}\``).join(', ')}`);
|
|
540
|
-
}
|
|
541
|
-
lines.push(` • Confirm the top failure mode above can't recur in your change`);
|
|
542
|
-
lines.push(` • Have a rollback ready for any \`critical\`/\`major\` step`);
|
|
543
|
-
lines.push('');
|
|
544
|
-
}
|
|
545
|
-
lines.push(`💡 After you ship: \`learn_from_attempts(topic="...", outcome="success", ...)\` → the next plan gets sharper.`);
|
|
546
|
-
return lines.join('\n');
|
|
547
|
-
}
|
|
548
|
-
// ── W10: Domain Brains marketplace — brain_marketplace ──────────────────────
|
|
549
|
-
// Browse curated, installable packs of high-trust community lessons by domain.
|
|
550
|
-
case 'brain_marketplace': {
|
|
551
|
-
const { min_confirms = 1 } = args;
|
|
552
|
-
const params = new URLSearchParams();
|
|
553
|
-
if (min_confirms && min_confirms > 1)
|
|
554
|
-
params.set('min_confirms', String(min_confirms));
|
|
555
|
-
const res = await apiFetch(`/api/v1/syndication/brains${params.toString() ? `?${params}` : ''}`);
|
|
556
|
-
if (!res.brains || res.brains.length === 0) {
|
|
557
|
-
return [
|
|
558
|
-
`## 🧠 Domain Brain Marketplace`,
|
|
559
|
-
``,
|
|
560
|
-
`No domain brains are available yet — the marketplace fills as the community`,
|
|
561
|
-
`contributes verified lessons. Be an early contributor:`,
|
|
562
|
-
`\`syndicate(topic="k8s:oom-kill", what_worked="raise memory limit + add probe")\``,
|
|
563
|
-
].join('\n');
|
|
564
|
-
}
|
|
565
|
-
const lines = [
|
|
566
|
-
`## 🧠 Domain Brain Marketplace`,
|
|
567
|
-
`*${res.count} installable brain${res.count === 1 ? '' : 's'} · curated from the global Knowledge Commons*`,
|
|
568
|
-
``,
|
|
569
|
-
];
|
|
570
|
-
for (const b of res.brains) {
|
|
571
|
-
const badge = b.curated ? '⭐ curated' : 'community';
|
|
572
|
-
lines.push(`### ${b.icon} ${b.name} \`${b.slug}\``, `${b.description}`, `**${b.lesson_count}** lesson${b.lesson_count === 1 ? '' : 's'} · 🤝 ${b.total_confirms} community confirm${b.total_confirms === 1 ? '' : 's'} · ${badge}`, `Install: \`brain_install(slug="${b.slug}")\``, ``);
|
|
573
|
-
}
|
|
574
|
-
lines.push(`---`, `_Installed brains merge into your local Brain and surface in \`smart_recall\` — they never override your own lessons._`);
|
|
575
|
-
return lines.join('\n');
|
|
576
|
-
}
|
|
577
|
-
// ── W10: Domain Brains marketplace — brain_install ──────────────────────────
|
|
578
|
-
// Pull a domain brain's curated lessons into the local Brain so they're
|
|
579
|
-
// available offline in smart_recall. Idempotent + non-destructive: never
|
|
580
|
-
// clobbers the user's own lessons (only prior marketplace installs).
|
|
581
|
-
case 'brain_install': {
|
|
582
|
-
const { instance_id, slug, min_confirms = 1, limit = 200, dry_run = false } = args;
|
|
583
|
-
const cleanSlug = typeof slug === 'string' ? slug.trim() : '';
|
|
584
|
-
if (!cleanSlug) {
|
|
585
|
-
throw new McpError(ErrorCode.InvalidParams, 'slug is required — run brain_marketplace() to see available domain brains');
|
|
586
|
-
}
|
|
587
|
-
const params = new URLSearchParams();
|
|
588
|
-
if (min_confirms && min_confirms > 1)
|
|
589
|
-
params.set('min_confirms', String(min_confirms));
|
|
590
|
-
if (limit && limit !== 200)
|
|
591
|
-
params.set('limit', String(Math.min(Math.max(1, limit), 500)));
|
|
592
|
-
const pack = await apiFetch(`/api/v1/syndication/brains/${encodeURIComponent(cleanSlug)}${params.toString() ? `?${params}` : ''}`);
|
|
593
|
-
if (!pack.lessons || pack.lessons.length === 0) {
|
|
594
|
-
return `No installable lessons found for domain brain \`${cleanSlug}\`. Run \`brain_marketplace()\` to see what's available.`;
|
|
595
|
-
}
|
|
596
|
-
if (dry_run) {
|
|
597
|
-
return [
|
|
598
|
-
`## 📦 ${pack.name} (DRY RUN)`,
|
|
599
|
-
`Would install **${pack.lessons.length}** lesson${pack.lessons.length === 1 ? '' : 's'} into your Brain.`,
|
|
600
|
-
``,
|
|
601
|
-
...pack.lessons.slice(0, 15).map(l => ` • \`${l.topic}\` · 🤝 ${l.confirm_count}`),
|
|
602
|
-
pack.lessons.length > 15 ? ` …and ${pack.lessons.length - 15} more` : '',
|
|
603
|
-
``,
|
|
604
|
-
`Run without \`dry_run\` to install: \`brain_install(slug="${cleanSlug}")\``,
|
|
605
|
-
].filter(Boolean).join('\n');
|
|
606
|
-
}
|
|
607
|
-
const redis = await getConnection(instance_id);
|
|
608
|
-
const source = `marketplace:${cleanSlug}`;
|
|
609
|
-
const now = new Date().toISOString();
|
|
610
|
-
let installed = 0;
|
|
611
|
-
let skipped = 0;
|
|
612
|
-
for (const l of pack.lessons) {
|
|
613
|
-
if (!l.topic || !l.what_worked) {
|
|
614
|
-
skipped++;
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
const bestKey = `cachly:lesson:best:${l.topic}`;
|
|
618
|
-
// Never clobber a user's own lesson (or a prior install from a different brain).
|
|
619
|
-
const existing = await redis.get(bestKey);
|
|
620
|
-
if (existing) {
|
|
621
|
-
const parsed = safeJsonParse(existing, null);
|
|
622
|
-
if (parsed && parsed.source !== source) {
|
|
623
|
-
skipped++;
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
// Tags arrive as a JSON string from the commons; normalise to an array.
|
|
628
|
-
const tags = (() => {
|
|
629
|
-
const t = safeJsonParse(l.tags, []);
|
|
630
|
-
return Array.isArray(t) ? t.filter((x) => typeof x === 'string') : [];
|
|
631
|
-
})();
|
|
632
|
-
const record = JSON.stringify({
|
|
633
|
-
topic: l.topic,
|
|
634
|
-
outcome: l.outcome || 'success',
|
|
635
|
-
what_worked: l.what_worked,
|
|
636
|
-
what_failed: l.what_failed || '',
|
|
637
|
-
severity: l.severity || 'minor',
|
|
638
|
-
tags,
|
|
639
|
-
visibility: 'team',
|
|
640
|
-
confidence: calculateConfidence({ ts: now, verified_at: now, recall_count: l.confirm_count }),
|
|
641
|
-
recall_count: 0,
|
|
642
|
-
source,
|
|
643
|
-
ts: now,
|
|
644
|
-
verified_at: now,
|
|
645
|
-
version: 3,
|
|
646
|
-
});
|
|
647
|
-
await redis.set(bestKey, record);
|
|
648
|
-
const listKey = `cachly:lessons:${l.topic}`;
|
|
649
|
-
await redis.rpush(listKey, record);
|
|
650
|
-
await redis.ltrim(listKey, -100, -1);
|
|
651
|
-
await redis.expire(listKey, 90 * 86400);
|
|
652
|
-
installed++;
|
|
653
|
-
}
|
|
654
|
-
// Stamp born_at so time-to-first-recall counts from the install moment.
|
|
655
|
-
await redis.set(`cachly:stats:born_at:${instance_id}`, now, 'EX', 365 * 86400, 'NX').catch(() => { });
|
|
656
|
-
return [
|
|
657
|
-
`## 📦 Installed: ${pack.name}`,
|
|
658
|
-
``,
|
|
659
|
-
`**${installed}** lesson${installed === 1 ? '' : 's'} merged into your Brain${skipped > 0 ? ` · ${skipped} skipped (your own lessons take priority)` : ''}.`,
|
|
660
|
-
``,
|
|
661
|
-
`They're live in \`smart_recall\` now — try a query in this domain:`,
|
|
662
|
-
` • \`smart_recall(query="${pack.lessons[0]?.topic.split(':').pop() ?? 'your problem'}")\``,
|
|
663
|
-
``,
|
|
664
|
-
`_Installed lessons are tagged \`source: "${source}"\` and never override your own. Re-run anytime to pull updates._`,
|
|
665
|
-
].join('\n');
|
|
666
|
-
}
|
|
667
|
-
// ── brain_conflicts: list live belief conflicts + active agents ─────────────
|
|
668
|
-
// Surfaces every unresolved belief_conflict marker (a previously confirmed
|
|
669
|
-
// fix now contradicted by a failure) plus the agents currently writing to
|
|
670
|
-
// this Brain. This is the arbitration inbox for multi-agent teams (Move 4).
|
|
671
|
-
case 'brain_conflicts': {
|
|
672
|
-
const { instance_id } = args;
|
|
673
|
-
const redis = await getConnection(instance_id);
|
|
674
|
-
const conflictKeys = [];
|
|
675
|
-
const cStream = redis.scanStream({ match: 'cachly:ckg:conflict:*', count: 100 });
|
|
676
|
-
await new Promise((resolve, reject) => {
|
|
677
|
-
cStream.on('data', (batch) => conflictKeys.push(...batch));
|
|
678
|
-
cStream.on('end', resolve);
|
|
679
|
-
cStream.on('error', reject);
|
|
680
|
-
});
|
|
681
|
-
const conflicts = [];
|
|
682
|
-
if (conflictKeys.length > 0) {
|
|
683
|
-
const raws = await redis.mget(...conflictKeys);
|
|
684
|
-
for (const raw of raws) {
|
|
685
|
-
const c = safeJsonParse(raw ?? null, null);
|
|
686
|
-
if (c && !c.resolved)
|
|
687
|
-
conflicts.push(c);
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
const agentKeys = [];
|
|
691
|
-
const aStream = redis.scanStream({ match: 'cachly:agents:active:*', count: 100 });
|
|
692
|
-
await new Promise((resolve, reject) => {
|
|
693
|
-
aStream.on('data', (batch) => agentKeys.push(...batch));
|
|
694
|
-
aStream.on('end', resolve);
|
|
695
|
-
aStream.on('error', reject);
|
|
696
|
-
});
|
|
697
|
-
const agents = [];
|
|
698
|
-
if (agentKeys.length > 0) {
|
|
699
|
-
const raws = await redis.mget(...agentKeys);
|
|
700
|
-
for (const raw of raws) {
|
|
701
|
-
const a = safeJsonParse(raw ?? null, null);
|
|
702
|
-
if (a)
|
|
703
|
-
agents.push(a);
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
const lines = [`## ⚔️ Brain Conflicts & Live Agents`, ``];
|
|
707
|
-
lines.push(`**🤖 Active agents (last 1h):** ${agents.length}`);
|
|
708
|
-
if (agents.length > 0) {
|
|
709
|
-
for (const a of agents.sort((x, y) => y.ts.localeCompare(x.ts)).slice(0, 10)) {
|
|
710
|
-
const ago = Math.round((Date.now() - new Date(a.ts).getTime()) / 60000);
|
|
711
|
-
lines.push(` - **${a.author}** — last: \`${a.last_topic ?? '?'}\` (${a.last_outcome ?? '?'}, ${ago}m ago)`);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
lines.push('');
|
|
715
|
-
if (conflicts.length === 0) {
|
|
716
|
-
lines.push(`**✅ No unresolved belief conflicts.** The Brain is in consensus.`);
|
|
717
|
-
return lines.join('\n');
|
|
718
|
-
}
|
|
719
|
-
lines.push(`**⚠️ Unresolved belief conflicts:** ${conflicts.length}`);
|
|
720
|
-
lines.push('');
|
|
721
|
-
lines.push(`| Topic | Confirmed fix | Contradicted by | Detected |`);
|
|
722
|
-
lines.push(`|---|---|---|---|`);
|
|
723
|
-
for (const c of conflicts.sort((x, y) => y.detected_at.localeCompare(x.detected_at)).slice(0, 20)) {
|
|
724
|
-
const ago = Math.round((Date.now() - new Date(c.detected_at).getTime()) / 3600000);
|
|
725
|
-
lines.push(`| \`${c.topic}\` | ${(c.fix_confidence * 100).toFixed(0)}% (n=${c.fix_trials}) | ${c.reported_by ?? 'unknown'} | ${ago}h ago |`);
|
|
726
|
-
}
|
|
727
|
-
lines.push('');
|
|
728
|
-
lines.push(`_Arbitrate with \`brain_resolve_conflict(instance_id="${instance_id}", topic="<topic>", winner="success"|"failure")\`._`);
|
|
729
|
-
return lines.join('\n');
|
|
730
|
-
}
|
|
731
|
-
// ── brain_resolve_conflict: arbitrate a belief conflict ─────────────────────
|
|
732
|
-
// Picks a winning side for a contested topic. The losing side's CKG fixes
|
|
733
|
-
// edges are decayed to near-zero confidence and the loser's `best` lesson is
|
|
734
|
-
// archived; the winner is reinforced. The resolution is recorded so the
|
|
735
|
-
// conflict no longer surfaces. Human-in-the-loop is the strongest signal.
|
|
736
|
-
case 'brain_resolve_conflict': {
|
|
737
|
-
const { instance_id, topic, winner, resolved_by = 'human' } = args;
|
|
738
|
-
if (!topic || !topic.trim()) {
|
|
739
|
-
throw new McpError(ErrorCode.InvalidParams, 'topic is required');
|
|
740
|
-
}
|
|
741
|
-
if (winner !== 'success' && winner !== 'failure') {
|
|
742
|
-
throw new McpError(ErrorCode.InvalidParams, 'winner must be "success" or "failure"');
|
|
743
|
-
}
|
|
744
|
-
const redis = await getConnection(instance_id);
|
|
745
|
-
const conceptId = ckgSlug(topic);
|
|
746
|
-
const conflictKey = `cachly:ckg:conflict:${conceptId}`;
|
|
747
|
-
const markerRaw = await redis.get(conflictKey);
|
|
748
|
-
if (!markerRaw) {
|
|
749
|
-
return `📭 No active conflict found for \`${topic}\`. Run \`brain_conflicts(instance_id="${instance_id}")\` to list open conflicts.`;
|
|
750
|
-
}
|
|
751
|
-
// ── Decay the losing side's `fixes` edges ─────────────────────────────────
|
|
752
|
-
// winner=success → the fix is real; keep fixes edges, no decay.
|
|
753
|
-
// winner=failure → the fix is wrong; decay fixes edges to 0.1.
|
|
754
|
-
let decayedEdges = 0;
|
|
755
|
-
if (winner === 'failure') {
|
|
756
|
-
const fromKeys = await redis.smembers(`cachly:ckg:idx:from:${conceptId}`);
|
|
757
|
-
for (const ek of fromKeys) {
|
|
758
|
-
const er = await redis.get(ek);
|
|
759
|
-
if (!er)
|
|
760
|
-
continue;
|
|
761
|
-
const edge = safeJsonParse(er, null);
|
|
762
|
-
if (!edge || edge.edgeType !== 'fixes')
|
|
763
|
-
continue;
|
|
764
|
-
edge.confidence = 0.1;
|
|
765
|
-
await redis.set(ek, JSON.stringify(edge));
|
|
766
|
-
decayedEdges++;
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
// ── Archive the losing `best` lesson (if its outcome is the losing side) ──
|
|
770
|
-
let archivedLesson = false;
|
|
771
|
-
const bestKey = `cachly:lesson:best:${topic}`;
|
|
772
|
-
const bestRaw = await redis.get(bestKey);
|
|
773
|
-
if (bestRaw) {
|
|
774
|
-
const lesson = safeJsonParse(bestRaw, null);
|
|
775
|
-
if (lesson && lesson.outcome === winner) {
|
|
776
|
-
// The surviving lesson matches the winner — reinforce by clearing any provisional state.
|
|
777
|
-
if (lesson.state === 'provisional') {
|
|
778
|
-
lesson.state = 'active';
|
|
779
|
-
lesson.verified_at = new Date().toISOString();
|
|
780
|
-
await redis.set(bestKey, JSON.stringify(lesson));
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
else if (lesson && lesson.outcome && lesson.outcome !== winner) {
|
|
784
|
-
// The stored best lesson is the losing side — archive it.
|
|
785
|
-
lesson.state = 'archived';
|
|
786
|
-
lesson.audit_trail = [
|
|
787
|
-
...(Array.isArray(lesson.audit_trail) ? lesson.audit_trail : []),
|
|
788
|
-
{ ts: new Date().toISOString(), action: 'conflict_loser_archived', resolved_by },
|
|
789
|
-
];
|
|
790
|
-
await redis.set(bestKey, JSON.stringify(lesson));
|
|
791
|
-
archivedLesson = true;
|
|
792
|
-
}
|
|
793
|
-
}
|
|
794
|
-
// ── Mark the conflict resolved ────────────────────────────────────────────
|
|
795
|
-
const marker = safeJsonParse(markerRaw, null) ?? {};
|
|
796
|
-
marker.resolved = true;
|
|
797
|
-
marker.winner = winner;
|
|
798
|
-
marker.resolved_by = resolved_by;
|
|
799
|
-
marker.resolved_at = new Date().toISOString();
|
|
800
|
-
await redis.set(conflictKey, JSON.stringify(marker), 'EX', 60 * 60 * 24 * 30);
|
|
801
|
-
return [
|
|
802
|
-
`## ✅ Conflict resolved — \`${topic}\``,
|
|
803
|
-
``,
|
|
804
|
-
`**Winner:** ${winner === 'success' ? '✅ success (fix is valid)' : '❌ failure (fix retired)'}`,
|
|
805
|
-
`**Resolved by:** ${resolved_by}`,
|
|
806
|
-
``,
|
|
807
|
-
`- CKG \`fixes\` edges decayed: **${decayedEdges}**`,
|
|
808
|
-
`- Losing lesson archived: **${archivedLesson ? 'yes' : 'no'}**`,
|
|
809
|
-
``,
|
|
810
|
-
winner === 'failure'
|
|
811
|
-
? `_The contradicted fix is retired. smart_recall will stop surfacing it; future attempts start fresh._`
|
|
812
|
-
: `_The fix is reaffirmed. The contradicting failure no longer blocks recall._`,
|
|
813
|
-
].join('\n');
|
|
814
|
-
}
|
|
815
|
-
// ── v4 Move 1: brain_confirm_ci — closed-loop CI learning ────────────────
|
|
816
|
-
case 'brain_confirm_ci': {
|
|
817
|
-
const { instance_id, job_status, topics, scan_topics = [], source = 'manual', } = args;
|
|
818
|
-
if (!job_status || !['success', 'failure', 'cancelled'].includes(job_status)) {
|
|
819
|
-
throw new Error('job_status must be success, failure, or cancelled');
|
|
820
|
-
}
|
|
821
|
-
if (!topics?.length)
|
|
822
|
-
throw new Error('topics is required');
|
|
823
|
-
if (job_status === 'cancelled') {
|
|
824
|
-
return '⏭️ CI was cancelled — no confidence changes applied.';
|
|
825
|
-
}
|
|
826
|
-
let result;
|
|
827
|
-
try {
|
|
828
|
-
result = await apiFetch(`/api/v1/instances/${instance_id}/ci-outcome`, {
|
|
829
|
-
method: 'POST',
|
|
830
|
-
body: JSON.stringify({ job_status, topics, scan_topics, source }),
|
|
831
|
-
});
|
|
832
|
-
}
|
|
833
|
-
catch {
|
|
834
|
-
return `⚠️ Could not reach Brain API — CI outcome not recorded. Will retry on next call.`;
|
|
835
|
-
}
|
|
836
|
-
if (result.updated === 0) {
|
|
837
|
-
return [
|
|
838
|
-
`🤖 **brain_confirm_ci** — CI ${job_status}`,
|
|
839
|
-
'',
|
|
840
|
-
`No confidence changes (no matching predictions for these topics).`,
|
|
841
|
-
`Topics checked: ${topics.slice(0, 5).map(t => `\`${t}\``).join(', ')}${topics.length > 5 ? ` +${topics.length - 5} more` : ''}`,
|
|
842
|
-
].join('\n');
|
|
843
|
-
}
|
|
844
|
-
const lines = [
|
|
845
|
-
`🤖 **brain_confirm_ci** — CI ${job_status === 'success' ? '✅' : '❌'} · ${result.updated} lesson${result.updated !== 1 ? 's' : ''} updated`,
|
|
846
|
-
'',
|
|
847
|
-
`| Topic | Old | New | Δ | Reason |`,
|
|
848
|
-
`|---|---|---|---|---|`,
|
|
849
|
-
];
|
|
850
|
-
for (const d of result.confidence_deltas) {
|
|
851
|
-
const sign = d.delta > 0 ? '+' : '';
|
|
852
|
-
const icon = d.reason === 'confirmed_failure' ? '🔴' : '🟡';
|
|
853
|
-
lines.push(`| \`${d.topic}\` | ${(d.old * 100).toFixed(0)}% | ${(d.new * 100).toFixed(0)}% | ${sign}${(d.delta * 100).toFixed(0)}% | ${icon} ${d.reason} |`);
|
|
854
|
-
}
|
|
855
|
-
lines.push('', `_Brain self-calibrated from real CI signal. No manual \`learn_from_attempts\` needed._`);
|
|
856
|
-
return lines.join('\n');
|
|
857
|
-
}
|
|
858
|
-
case 'brain_briefing': {
|
|
859
|
-
const { instance_id, event_type, context, threshold, } = args;
|
|
860
|
-
if (!['file_open', 'pr_open', 'deploy', 'manual'].includes(event_type)) {
|
|
861
|
-
throw new Error('event_type must be file_open, pr_open, deploy, or manual');
|
|
862
|
-
}
|
|
863
|
-
if (!context?.trim())
|
|
864
|
-
throw new Error('context is required');
|
|
865
|
-
let result;
|
|
866
|
-
try {
|
|
867
|
-
result = await apiFetch(`/api/v1/instances/${instance_id}/briefing`, {
|
|
868
|
-
method: 'POST',
|
|
869
|
-
body: JSON.stringify({ event_type, context, ...(threshold !== undefined ? { threshold } : {}) }),
|
|
870
|
-
});
|
|
871
|
-
}
|
|
872
|
-
catch {
|
|
873
|
-
return `⚠️ Could not reach Brain API — no proactive briefing available right now.`;
|
|
874
|
-
}
|
|
875
|
-
const eventIcon = event_type === 'file_open' ? '📂' : event_type === 'pr_open' ? '🔀' : event_type === 'deploy' ? '🚀' : '🔍';
|
|
876
|
-
if (result.warnings.length === 0) {
|
|
877
|
-
return [
|
|
878
|
-
`${eventIcon} **brain_briefing** — ${event_type} · ✅ no known risk patterns matched`,
|
|
879
|
-
'',
|
|
880
|
-
`Checked against ${result.matched_lessons} related lesson${result.matched_lessons !== 1 ? 's' : ''}. You're clear to proceed.`,
|
|
881
|
-
].join('\n');
|
|
882
|
-
}
|
|
883
|
-
const riskIcon = result.risk_level === 'high' ? '🔴' : result.risk_level === 'medium' ? '🟡' : '🟢';
|
|
884
|
-
const lines = [
|
|
885
|
-
`${eventIcon} **brain_briefing** — ${event_type} · ${riskIcon} risk: ${result.risk_level.toUpperCase()}`,
|
|
886
|
-
'',
|
|
887
|
-
`The Brain proactively found ${result.warnings.length} known pattern${result.warnings.length !== 1 ? 's' : ''} that may apply here:`,
|
|
888
|
-
'',
|
|
889
|
-
`| Topic | Confidence | Severity | What to watch | Known fix |`,
|
|
890
|
-
`|---|---|---|---|---|`,
|
|
891
|
-
];
|
|
892
|
-
for (const w of result.warnings) {
|
|
893
|
-
const fix = w.fix ? (w.fix.length > 80 ? w.fix.slice(0, 80) + '…' : w.fix) : '—';
|
|
894
|
-
lines.push(`| \`${w.topic}\` | ${(w.confidence * 100).toFixed(0)}% | ${w.severity} | ${w.message} | ${fix} |`);
|
|
895
|
-
}
|
|
896
|
-
lines.push('', `_Surfaced proactively — no \`smart_recall\` needed. Address the highest-confidence items first._`);
|
|
897
|
-
return lines.join('\n');
|
|
898
|
-
}
|
|
899
|
-
// ── Layer 3: MADC ─────────────────────────────────────────────────────────
|
|
900
|
-
default:
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
//# sourceMappingURL=syndicate.js.map
|
|
1
|
+
import{McpError as T,ErrorCode as C}from"@modelcontextprotocol/sdk/types.js";import{calculateConfidence as I}from"../confidence.js";import{ckgSlug as M}from"../ckg.js";import{keywordSearch as K}from"../search.js";import{safeJsonParse as S}from"../utils.js";async function B(x,y,v,b,a){const t=(await a(`/api/v1/instances/${x}`).catch(()=>null))?.org_id;if(!t)return null;let c=null;try{c=await a(`/api/v1/orgs/${t}/brain/predict`,{method:"POST",body:JSON.stringify({context:y,top_k:v})})}catch{return null}return!c||!c.warnings||c.warnings.length===0?null:await O(y,c,b,a)}async function N(x,y,v){const b=y.warnings[0]?.problem||x;if(!b)return[];try{const a=await v(`/api/v1/syndication/search?q=${encodeURIComponent(b)}&limit=3`),n=new Set(y.warnings.map(t=>t.problem));return(a.results??[]).filter(t=>!!t.topic&&!!t.what_worked&&!n.has(t.topic)).slice(0,3).map(t=>({topic:t.topic,what_worked:t.what_worked.slice(0,160)}))}catch{return[]}}async function O(x,y,v,b){const a=[`\u{1F52E} **Org Brain Predict: "${x}"**`,""];for(const n of y.warnings){const t=Math.max(0,n.team_span-1),c=n.recurrence.reduce((l,r)=>l+(r.count??0),0),s=n.severity==="critical"?"\u{1F534}":n.severity==="major"?"\u{1F7E0}":"\u{1F7E1}";n.team_span>=2?a.push(`\u26A0\uFE0F **CROSS-TEAM RISK** \u2014 this pattern failed ${c}\xD7 across ${t} other team${t===1?"":"s"}:`):a.push(`\u26A0\uFE0F **RISK** \u2014 this pattern failed ${c}\xD7 before:`),a.push(` ${s} problem: \`${n.problem}\``);for(const l of n.recurrence.slice(0,6)){const r=l.last_seen?` (last: ${l.last_seen.slice(0,10)})`:"";a.push(` \u2022 ${l.team} \xD7${l.count}${r}`)}const f=n.fixes[0];if(f){const l=Math.round((f.confidence??0)*100),r=f.source_team?` (${f.source_team})`:"";a.push(` \u2705 Proven fix \u2014 ${l}% confidence, confirmed ${f.confirm_count}\xD7${r}:`),a.push(` ${f.summary}`)}a.push("")}if(v==="org+commons"){const n=await N(x,y,b);if(n.length>0){a.push("\u{1F310} **From the public Knowledge Commons** (cross-org, lower-signal):");for(const t of n)a.push(` \u2022 \`${t.topic}\` \u2014 ${t.what_worked}`);a.push("")}}return a.push('\u{1F4A1} Heeded the warning? `learn_from_attempts(..., tags=["prevented-by-org-graph"])` \u2192 counts as a prevented cross-team mistake.'),a.join(`
|
|
2
|
+
`)}const A=new Set(["syndicate","syndicate_search","syndicate_stats","syndicate_trending","brain_search","ckg_inspect","brain_predict","brain_plan","brain_marketplace","brain_install","brain_conflicts","brain_resolve_conflict","brain_confirm_ci","brain_briefing"]);async function W(x,y,v,b){switch(x){case"syndicate":{const{topic:a,outcome:n="success",what_worked:t,what_failed:c="",severity:s="minor",tags:f=[],scope:l="public"}=y;if(!a||!t)throw new T(C.InvalidParams,"topic and what_worked are required");const o=await b("/api/v1/syndication/contribute",{method:"POST",body:JSON.stringify({topic:a,outcome:n,what_worked:t,what_failed:c,severity:s,tags:f,scope:l})}),p=l==="org"?"\u{1F3E2} org-private":"\u{1F310} global commons",u=o.deduped?`
|
|
3
|
+
> \u267B\uFE0F Duplicate detected \u2014 trust score incremented for the existing lesson.`:"";return[`${l==="org"?"\u{1F3E2}":"\u{1F310}"} **Lesson syndicated to the ${l==="org"?"org Knowledge Commons":"global Knowledge Commons"}**${u}`,"",`**ID:** \`${o.id}\``,`**Topic:** \`${o.topic}\` \xB7 **Outcome:** ${o.outcome} \xB7 **Scope:** ${p}`,"",l==="org"?'This lesson is visible only within your organisation. Use `syndicate_search(scope="org")` to find it.':"Your lesson is now searchable by every AI brain in the network.","When another instance confirms it works, its trust score rises \u2014 and so does your contributor reputation.","",`**Tip:** Use \`syndicate_search(q="${a}")\` to see all community lessons on this topic.`].join(`
|
|
4
|
+
`)}case"syndicate_search":{const{q:a="",limit:n=20,category:t="",scope:c=""}=y,s=new URLSearchParams;a&&s.set("q",a),t&&s.set("category",t),c&&s.set("scope",c),s.set("limit",String(Math.min(Math.max(1,n),50)));const f=await b(`/api/v1/syndication/search?${s}`);if(!f.results||f.results.length===0)return a?`No lessons found for "${a}" in the global Knowledge Commons yet.
|
|
5
|
+
|
|
6
|
+
Be the first to contribute: \`syndicate(topic="...", what_worked="...")\``:'The global Knowledge Commons is empty. Be the first contributor:\n`syndicate(topic="deploy:api", what_worked="...")`';const l=i=>i==="success"?"\u2705":i==="failure"?"\u274C":"\u26A0\uFE0F",r=i=>i==="critical"?"\u{1F534}":i==="major"?"\u{1F7E1}":"\u{1F7E2}",o=i=>{const d=Math.min(10,Math.round(i/5));return"\u2588".repeat(d)+"\u2591".repeat(10-d)+` \xD7${i}`},p=[a,t].filter(Boolean).join(" \xB7 "),u=[`## \u{1F310} Global Knowledge Commons${p?` \u2014 ${p}`:" \u2014 Recent"}`,`*${f.count} lesson${f.count===1?"":"s"} found*`,""];for(const i of f.results)u.push(`### ${l(i.outcome)} \`${i.topic}\` ${r(i.severity)}`,`**Trust:** ${o(i.confirm_count)}`,i.what_worked?`**What worked:** ${i.what_worked}`:"",i.what_failed?`**What failed:** ${i.what_failed}`:"",`*Contributed ${new Date(i.created_at).toLocaleDateString("de-DE")} \xB7 ID: \`${i.id}\`*`,"");return u.push("---",`**Confirm** (this helped you): \`syndicate(topic="${f.results[0]?.topic??"..."}", what_worked="...")\` \u2192 auto-deduped, trust +1`,'**Contribute your own:** `syndicate(topic="fix:...", what_worked="...")`','**Filter by category:** `syndicate_search(category="fix")`'),u.filter(i=>i!=="").join(`
|
|
7
|
+
`)}case"syndicate_stats":{const a=await b("/api/v1/syndication/stats"),n=c=>{const s=Math.min(10,Math.round(c/5));return"\u2588".repeat(s)+"\u2591".repeat(10-s)+` \xD7${c}`},t=["## \u{1F310} Global Knowledge Commons \u2014 Stats","","| Metric | Value |","|---|---|",`| Total lessons | **${a.total_lessons.toLocaleString()}** |`,`| Total confirms | **${a.total_confirms.toLocaleString()}** |`,`| Added last 7 days | **${a.added_last_7_days}** |`,"","### Top Categories"];for(const c of a.top_categories??[])t.push(`- \`${c.category}\` \u2014 ${c.count} lesson${c.count===1?"":"s"}`);t.push("","### Most Trusted Lessons");for(const c of a.most_trusted??[])t.push(`**\`${c.topic}\`** ${n(c.confirm_count)}`,`> ${c.what_worked.slice(0,120)}${c.what_worked.length>120?"\u2026":""}`,"");if(t.push("---",'**Contribute:** `syndicate(topic="...", what_worked="...")` | **Search:** `syndicate_search(q="your problem")`'),a.top_contributors?.length){t.push("","### \u{1F3C5} Top Contributors (anonymous)");for(const c of a.top_contributors)t.push(`- Trust **${c.trust_score}** \xB7 ${c.lessons_count} lesson${c.lessons_count===1?"":"s"} \xB7 ${c.confirms_received} confirms received`)}return t.join(`
|
|
8
|
+
`)}case"syndicate_trending":{const{limit:a=10}=y,n=new URLSearchParams({limit:String(Math.min(Math.max(1,a),50))}),t=await b(`/api/v1/syndication/trending?${n}`);if(!t.results||t.results.length===0)return["## \u{1F4C8} Trending in the Knowledge Commons","","No trending lessons yet (need at least 2 confirms in the last 7 days).","",'Contribute and confirm lessons to see them trend: `syndicate(topic="...", what_worked="...")`'].join(`
|
|
9
|
+
`);const c=o=>o==="success"?"\u2705":o==="failure"?"\u274C":"\u26A0\uFE0F",s=o=>o==="critical"?"\u{1F534}":o==="major"?"\u{1F7E1}":"\u{1F7E2}",f=o=>{const p=Math.min(10,Math.round(o/5));return"\u2588".repeat(p)+"\u2591".repeat(10-p)+` \xD7${o}`},l=o=>{const p=Math.min(10,Math.round(o*2));return"\u25B2".repeat(p)+"\u25B3".repeat(10-p)+` ${o.toFixed(2)}/day`},r=["## \u{1F4C8} Trending in the Knowledge Commons","*Lessons with the fastest confirmation velocity in the last 7 days*",""];for(const o of t.results)r.push(`### ${c(o.outcome)} \`${o.topic}\` ${s(o.severity)}`,`**Trend:** ${l(o.trend_score)} | **Trust:** ${f(o.confirm_count)}`,o.what_worked?`**What worked:** ${o.what_worked.slice(0,200)}${o.what_worked.length>200?"\u2026":""}`:"",`*ID: \`${o.id}\` \xB7 ${new Date(o.created_at).toLocaleDateString("de-DE")}*`,"");return r.push("---",`**Confirm** (if this helped you): \`syndicate(topic="${t.results[0]?.topic??"..."}", what_worked="...")\` \u2192 auto-deduped, trust +1`,'**All trending:** `syndicate_trending(limit=50)` | **Search:** `syndicate_search(q="...")`'),r.filter(o=>o!=="").join(`
|
|
10
|
+
`)}case"brain_search":{const{instance_id:a,query:n,limit:t=15}=y,c=await v(a),s=await K(c,["cachly:lesson:best:*","cachly:ctx:*","cachly:idx:*","cachly:session:last","cachly:session:handoff","cachly:roadmap:*","cachly:ckg:node:*"],n,t);if(s.length===0)return[`\u{1F50E} **Brain Search: "${n}"**`,"","No results found across all brain data.","","\u{1F4A1} Try `smart_recall` or check `list_remembered`."].join(`
|
|
11
|
+
`);const f=[`\u{1F50E} **Brain Search: "${n}"** \u2014 ${s.length} result${s.length!==1?"s":""} across all brain data
|
|
12
|
+
`];for(const l of s.slice(0,t)){const r=l.key.startsWith("cachly:lesson:")?"\u{1F4A1} lesson":l.key.startsWith("cachly:ctx:")?"\u{1F4DD} context":l.key.startsWith("cachly:idx:")?"\u{1F4C2} index":l.key.startsWith("cachly:session:")?"\u{1F550} session":l.key.startsWith("cachly:roadmap:")?"\u{1F5FA}\uFE0F roadmap":l.key.startsWith("cachly:ckg:node:")?"\u{1F578}\uFE0F ckg-node":"\u{1F5C4}\uFE0F data",o=l.content.slice(0,280).replace(/\n/g," ");f.push(`**${r}** \`${l.key.split(":").slice(2).join(":")}\` _(BM25: ${l.score.toFixed(2)})_`),f.push(`> ${o}${l.content.length>280?"\u2026":""}
|
|
13
|
+
`)}return f.join(`
|
|
14
|
+
`)}case"ckg_inspect":{const{instance_id:a,concept:n,max_hops:t=2}=y,c=await v(a),s=M(n),f=new Set,l=[],r=[{id:s,hop:0}];for(;r.length>0;){const{id:e,hop:h}=r.shift();if(f.has(e)||h>t)continue;f.add(e);const g=await c.smembers(`cachly:ckg:idx:from:${e}`),m=await c.smembers(`cachly:ckg:idx:to:${e}`);for(const w of[...g,...m].slice(0,50)){const $=await c.get(w);if(!$)continue;const _=S($,null);_&&(l.push(_),h<t&&(f.has(_.from)||r.push({id:_.from,hop:h+1}),f.has(_.to)||r.push({id:_.to,hop:h+1})))}}if(l.length===0){const e=[],h=c.scanStream({match:`cachly:ckg:node:*${s}*`,count:100});if(await new Promise((m,w)=>{h.on("data",$=>e.push(...$)),h.on("end",m),h.on("error",w)}),e.length===0)return[`\u{1F578}\uFE0F **CKG Inspect: "${n}"**`,"","No CKG nodes found. The graph builds automatically as you call `learn_from_attempts`.","","\u{1F4A1} Once you have lessons stored, CKG edges will appear here."].join(`
|
|
15
|
+
`);const g=e.slice(0,10).map(m=>` \u2022 \`${m.replace("cachly:ckg:node:","")}\``).join(`
|
|
16
|
+
`);return[`\u{1F578}\uFE0F **CKG Inspect: "${n}"**`,"",`No edges found for \`${s}\`, but found similar nodes:`,g,"",'Try: `ckg_inspect(concept="<exact-node-id>")`'].join(`
|
|
17
|
+
`)}const o=new Set,p=l.filter(e=>{const h=`${e.from}:${e.edgeType}:${e.to}`;return o.has(h)?!1:(o.add(h),!0)}).sort((e,h)=>h.confidence-e.confidence),u={fixes:"\u{1F527}",requires:"\u{1F517}","co-occurs":"\u{1F504}",causes:"\u26A1",contradicts:"\u26A0\uFE0F",degrades_under:"\u{1F4C9}"},i=[`\u{1F578}\uFE0F **CKG Inspect: "${n}"** (${p.length} edge${p.length!==1?"s":""}, ${f.size} node${f.size!==1?"s":""} traversed)
|
|
18
|
+
`],d=new Map;for(const e of p)d.has(e.edgeType)||d.set(e.edgeType,[]),d.get(e.edgeType).push(e);for(const[e,h]of d){const g=u[e]??"\u2192";i.push(`**${g} ${e}** (${h.length})`);for(const m of h.slice(0,8)){const w=Math.round(m.confidence*100),$="\u2593".repeat(Math.round(w/10))+"\u2591".repeat(10-Math.round(w/10));i.push(` \`${m.from}\` \u2192 \`${m.to}\` ${$} ${w}% (${m.successes.toFixed(1)}/${m.trials} trials)`)}i.push("")}return i.push(`\u{1F4A1} Expand: \`ckg_inspect(concept="${n}", max_hops=3)\` | Predict: \`brain_predict(context="${n}")\``),i.join(`
|
|
19
|
+
`)}case"brain_predict":{const{instance_id:a,context:n,top_k:t=5,scope:c="instance"}=y;if(c==="org"||c==="org+commons"){const i=await B(a,n,t,c,b);if(i!==null)return i}const s=await v(a),f=n.toLowerCase().replace(/[^a-z0-9\s\-_:]/g," ").split(/\s+/).filter(i=>i.length>2),l=[];for(const i of f.slice(0,6)){const d=[],e=s.scanStream({match:`cachly:ckg:node:*${i}*`,count:50});await new Promise((h,g)=>{e.on("data",m=>d.push(...m)),e.on("end",h),e.on("error",g)});for(const h of d.slice(0,5)){const g=await s.get(h);if(!g)continue;const m=S(g,null);if(!m)continue;const w=await s.smembers(`cachly:ckg:idx:from:${m.id}`);for(const $ of w.slice(0,20)){const _=await s.get($);if(!_)continue;const k=S(_,null);if(!k||k.edgeType!=="fixes"&&k.edgeType!=="co-occurs"&&k.edgeType!=="causes")continue;const j=await s.get(`cachly:lesson:best:${k.from}`),P=j?S(j,null)??void 0:void 0;l.push({concept:m.id,edgeType:k.edgeType,target:k.to,confidence:k.confidence,lesson:P})}}}const r=[],o=[],p=s.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((i,d)=>{p.on("data",e=>o.push(...e)),p.on("end",i),p.on("error",d)});for(const i of o){const d=await s.get(i);if(d){const e=S(d,null);if(!e)continue;const h=[e.topic,e.what_failed??"",e.what_worked??""].join(" ").toLowerCase();if(f.reduce((m,w)=>m+(h.includes(w)?1:0),0)>=1&&e.outcome!=="failure"){const m=I(e);r.push({...e,confidence:m})}}}if(r.sort((i,d)=>d.confidence-i.confidence),l.length===0&&r.length===0)return[`\u{1F52E} **Brain Predict: "${n}"**`,"","No predictions yet \u2014 the brain hasn't seen this domain.","","\u{1F4A1} As you solve problems in this area and call `learn_from_attempts`, the CKG builds up and predictions become available."].join(`
|
|
20
|
+
`);const u=[`\u{1F52E} **Brain Predict: "${n}"**
|
|
21
|
+
`];if(l.length>0){const i=new Set,d=l.filter(e=>{const h=`${e.concept}:${e.edgeType}:${e.target}`;return i.has(h)?!1:(i.add(h),!0)}).sort((e,h)=>h.confidence-e.confidence).slice(0,t);u.push(`### \u{1F578}\uFE0F CKG Predictions (based on ${d.length} known edges)`);for(const e of d){const h=Math.round(e.confidence*100),g=e.edgeType==="fixes"?"\u{1F527}":e.edgeType==="co-occurs"?"\u{1F504}":"\u26A1";u.push(`${g} **${h}%** \`${e.concept}\` _${e.edgeType}_ \`${e.target}\``),e.lesson?.what_worked&&u.push(` \u2705 ${e.lesson.what_worked.slice(0,120)}`)}u.push("")}if(r.length>0){u.push(`### \u{1F4DA} Relevant Lessons (${Math.min(r.length,t)} pre-loaded)`);for(const i of r.slice(0,t)){const d=Math.round(i.confidence*100);u.push(` \u2705 **${d}%** \`${i.topic}\` \u2014 ${(i.what_worked??"").slice(0,120)}`)}u.push("")}return u.push('\u{1F4A1} Outcome confirmed? `learn_from_attempts(topic="fix:...", outcome="success", ...)` \u2192 improves future predictions'),u.join(`
|
|
22
|
+
`)}case"brain_plan":{const{instance_id:a,task:n,top_k:t=5}=y;if(!n||!n.trim())throw new T(C.InvalidParams,"task is required");const c=await v(a),s=n.toLowerCase().replace(/[^a-z0-9\s\-_:]/g," ").split(/\s+/).filter(d=>d.length>2),f=[],l=c.scanStream({match:"cachly:lesson:best:*",count:200});await new Promise((d,e)=>{l.on("data",h=>f.push(...h)),l.on("end",d),l.on("error",e)});const r=[];for(const d of f){const e=await c.get(d);if(!e)continue;const h=S(e,null);if(!h)continue;const g=[h.topic,h.what_worked??"",h.what_failed??""].join(" ").toLowerCase(),m=s.reduce((w,$)=>w+(g.includes($)?1:0),0);m>=1&&r.push({l:h,score:m,confidence:I(h)})}if(r.length===0)return[`\u{1F5FA}\uFE0F **Brain Plan: "${n}"**`,"","No grounded plan yet \u2014 the brain hasn't seen this area.","","\u{1F4A1} As you work here and call `learn_from_attempts`, brain_plan will assemble"," an ordered, proven plan from your own history. For now, proceed carefully"," and capture what works."].join(`
|
|
23
|
+
`);r.sort((d,e)=>e.score-d.score||e.confidence-d.confidence);const o=r.filter(d=>d.l.outcome==="failure"||(d.l.what_failed??"").trim().length>0),p=r.filter(d=>d.l.outcome==="success"||d.l.outcome==="partial"),u=[`\u{1F5FA}\uFE0F **Brain Plan: "${n}"**`,"",`_Grounded in ${r.length} relevant lesson${r.length!==1?"s":""} from your own Brain._`,""];if(o.length>0){u.push("### \u26A0\uFE0F Likely failure modes (avoid these)");const d={critical:0,major:1,minor:2};o.slice().sort((e,h)=>(d[e.l.severity??"minor"]??1)-(d[h.l.severity??"minor"]??1)||h.confidence-e.confidence).slice(0,t).forEach(({l:e,confidence:h})=>{const g=e.severity==="critical"?"\u{1F534}":e.severity==="major"?"\u{1F7E1}":"\u{1F7E2}",m=(e.what_failed??e.what_worked??"").slice(0,120);u.push(`${g} **${Math.round(h*100)}%** \`${e.topic}\`${m?` \u2014 ${m}`:""}`)}),u.push("")}if(p.length>0){const d=p.slice(0,t),e=new Map(d.map(w=>[w.l.topic,w])),h=[],g=new Set,m=(w,$)=>{if(!(g.has(w.l.topic)||$>10)){for(const _ of w.l.depends_on??[]){const k=e.get(_);k&&!g.has(_)&&m(k,$+1)}g.has(w.l.topic)||(g.add(w.l.topic),h.push(w))}};for(const w of d)m(w,0);u.push("### \u{1F527} Recommended steps (proven fixes, in order)"),h.forEach(({l:w,confidence:$},_)=>{u.push(`${_+1}. **${Math.round($*100)}%** \`${w.topic}\` \u2014 ${(w.what_worked??"").slice(0,140)}`);const k=(w.commands??[]).slice(0,2);k.length>0&&u.push(` \`\`\`
|
|
24
|
+
${k.join(`
|
|
25
|
+
`)}
|
|
26
|
+
\`\`\``),(w.depends_on??[]).length>0&&u.push(` \u21B3 depends on: ${(w.depends_on??[]).map(j=>`\`${j}\``).join(", ")}`)}),u.push("")}const i=r.filter(d=>(d.l.file_paths??[]).length>0||(d.l.commands??[]).length>0).slice(0,4);if(i.length>0){u.push("### \u2705 Pre-flight checklist");const d=[...new Set(i.flatMap(e=>e.l.file_paths??[]))].slice(0,6);d.length>0&&u.push(` \u2022 Review files that were involved before: ${d.map(e=>`\`${e}\``).join(", ")}`),u.push(" \u2022 Confirm the top failure mode above can't recur in your change"),u.push(" \u2022 Have a rollback ready for any `critical`/`major` step"),u.push("")}return u.push('\u{1F4A1} After you ship: `learn_from_attempts(topic="...", outcome="success", ...)` \u2192 the next plan gets sharper.'),u.join(`
|
|
27
|
+
`)}case"brain_marketplace":{const{min_confirms:a=1}=y,n=new URLSearchParams;a&&a>1&&n.set("min_confirms",String(a));const t=await b(`/api/v1/syndication/brains${n.toString()?`?${n}`:""}`);if(!t.brains||t.brains.length===0)return["## \u{1F9E0} Domain Brain Marketplace","","No domain brains are available yet \u2014 the marketplace fills as the community","contributes verified lessons. Be an early contributor:",'`syndicate(topic="k8s:oom-kill", what_worked="raise memory limit + add probe")`'].join(`
|
|
28
|
+
`);const c=["## \u{1F9E0} Domain Brain Marketplace",`*${t.count} installable brain${t.count===1?"":"s"} \xB7 curated from the global Knowledge Commons*`,""];for(const s of t.brains){const f=s.curated?"\u2B50 curated":"community";c.push(`### ${s.icon} ${s.name} \`${s.slug}\``,`${s.description}`,`**${s.lesson_count}** lesson${s.lesson_count===1?"":"s"} \xB7 \u{1F91D} ${s.total_confirms} community confirm${s.total_confirms===1?"":"s"} \xB7 ${f}`,`Install: \`brain_install(slug="${s.slug}")\``,"")}return c.push("---","_Installed brains merge into your local Brain and surface in `smart_recall` \u2014 they never override your own lessons._"),c.join(`
|
|
29
|
+
`)}case"brain_install":{const{instance_id:a,slug:n,min_confirms:t=1,limit:c=200,dry_run:s=!1}=y,f=typeof n=="string"?n.trim():"";if(!f)throw new T(C.InvalidParams,"slug is required \u2014 run brain_marketplace() to see available domain brains");const l=new URLSearchParams;t&&t>1&&l.set("min_confirms",String(t)),c&&c!==200&&l.set("limit",String(Math.min(Math.max(1,c),500)));const r=await b(`/api/v1/syndication/brains/${encodeURIComponent(f)}${l.toString()?`?${l}`:""}`);if(!r.lessons||r.lessons.length===0)return`No installable lessons found for domain brain \`${f}\`. Run \`brain_marketplace()\` to see what's available.`;if(s)return[`## \u{1F4E6} ${r.name} (DRY RUN)`,`Would install **${r.lessons.length}** lesson${r.lessons.length===1?"":"s"} into your Brain.`,"",...r.lessons.slice(0,15).map(e=>` \u2022 \`${e.topic}\` \xB7 \u{1F91D} ${e.confirm_count}`),r.lessons.length>15?` \u2026and ${r.lessons.length-15} more`:"","",`Run without \`dry_run\` to install: \`brain_install(slug="${f}")\``].filter(Boolean).join(`
|
|
30
|
+
`);const o=await v(a),p=`marketplace:${f}`,u=new Date().toISOString();let i=0,d=0;for(const e of r.lessons){if(!e.topic||!e.what_worked){d++;continue}const h=`cachly:lesson:best:${e.topic}`,g=await o.get(h);if(g){const _=S(g,null);if(_&&_.source!==p){d++;continue}}const m=(()=>{const _=S(e.tags,[]);return Array.isArray(_)?_.filter(k=>typeof k=="string"):[]})(),w=JSON.stringify({topic:e.topic,outcome:e.outcome||"success",what_worked:e.what_worked,what_failed:e.what_failed||"",severity:e.severity||"minor",tags:m,visibility:"team",confidence:I({ts:u,verified_at:u,recall_count:e.confirm_count}),recall_count:0,source:p,ts:u,verified_at:u,version:3});await o.set(h,w);const $=`cachly:lessons:${e.topic}`;await o.rpush($,w),await o.ltrim($,-100,-1),await o.expire($,90*86400),i++}return await o.set(`cachly:stats:born_at:${a}`,u,"EX",365*86400,"NX").catch(()=>{}),[`## \u{1F4E6} Installed: ${r.name}`,"",`**${i}** lesson${i===1?"":"s"} merged into your Brain${d>0?` \xB7 ${d} skipped (your own lessons take priority)`:""}.`,"","They're live in `smart_recall` now \u2014 try a query in this domain:",` \u2022 \`smart_recall(query="${r.lessons[0]?.topic.split(":").pop()??"your problem"}")\``,"",`_Installed lessons are tagged \`source: "${p}"\` and never override your own. Re-run anytime to pull updates._`].join(`
|
|
31
|
+
`)}case"brain_conflicts":{const{instance_id:a}=y,n=await v(a),t=[],c=n.scanStream({match:"cachly:ckg:conflict:*",count:100});await new Promise((p,u)=>{c.on("data",i=>t.push(...i)),c.on("end",p),c.on("error",u)});const s=[];if(t.length>0){const p=await n.mget(...t);for(const u of p){const i=S(u??null,null);i&&!i.resolved&&s.push(i)}}const f=[],l=n.scanStream({match:"cachly:agents:active:*",count:100});await new Promise((p,u)=>{l.on("data",i=>f.push(...i)),l.on("end",p),l.on("error",u)});const r=[];if(f.length>0){const p=await n.mget(...f);for(const u of p){const i=S(u??null,null);i&&r.push(i)}}const o=["## \u2694\uFE0F Brain Conflicts & Live Agents",""];if(o.push(`**\u{1F916} Active agents (last 1h):** ${r.length}`),r.length>0)for(const p of r.sort((u,i)=>i.ts.localeCompare(u.ts)).slice(0,10)){const u=Math.round((Date.now()-new Date(p.ts).getTime())/6e4);o.push(` - **${p.author}** \u2014 last: \`${p.last_topic??"?"}\` (${p.last_outcome??"?"}, ${u}m ago)`)}if(o.push(""),s.length===0)return o.push("**\u2705 No unresolved belief conflicts.** The Brain is in consensus."),o.join(`
|
|
32
|
+
`);o.push(`**\u26A0\uFE0F Unresolved belief conflicts:** ${s.length}`),o.push(""),o.push("| Topic | Confirmed fix | Contradicted by | Detected |"),o.push("|---|---|---|---|");for(const p of s.sort((u,i)=>i.detected_at.localeCompare(u.detected_at)).slice(0,20)){const u=Math.round((Date.now()-new Date(p.detected_at).getTime())/36e5);o.push(`| \`${p.topic}\` | ${(p.fix_confidence*100).toFixed(0)}% (n=${p.fix_trials}) | ${p.reported_by??"unknown"} | ${u}h ago |`)}return o.push(""),o.push(`_Arbitrate with \`brain_resolve_conflict(instance_id="${a}", topic="<topic>", winner="success"|"failure")\`._`),o.join(`
|
|
33
|
+
`)}case"brain_resolve_conflict":{const{instance_id:a,topic:n,winner:t,resolved_by:c="human"}=y;if(!n||!n.trim())throw new T(C.InvalidParams,"topic is required");if(t!=="success"&&t!=="failure")throw new T(C.InvalidParams,'winner must be "success" or "failure"');const s=await v(a),f=M(n),l=`cachly:ckg:conflict:${f}`,r=await s.get(l);if(!r)return`\u{1F4ED} No active conflict found for \`${n}\`. Run \`brain_conflicts(instance_id="${a}")\` to list open conflicts.`;let o=0;if(t==="failure"){const e=await s.smembers(`cachly:ckg:idx:from:${f}`);for(const h of e){const g=await s.get(h);if(!g)continue;const m=S(g,null);!m||m.edgeType!=="fixes"||(m.confidence=.1,await s.set(h,JSON.stringify(m)),o++)}}let p=!1;const u=`cachly:lesson:best:${n}`,i=await s.get(u);if(i){const e=S(i,null);e&&e.outcome===t?e.state==="provisional"&&(e.state="active",e.verified_at=new Date().toISOString(),await s.set(u,JSON.stringify(e))):e&&e.outcome&&e.outcome!==t&&(e.state="archived",e.audit_trail=[...Array.isArray(e.audit_trail)?e.audit_trail:[],{ts:new Date().toISOString(),action:"conflict_loser_archived",resolved_by:c}],await s.set(u,JSON.stringify(e)),p=!0)}const d=S(r,null)??{};return d.resolved=!0,d.winner=t,d.resolved_by=c,d.resolved_at=new Date().toISOString(),await s.set(l,JSON.stringify(d),"EX",3600*24*30),[`## \u2705 Conflict resolved \u2014 \`${n}\``,"",`**Winner:** ${t==="success"?"\u2705 success (fix is valid)":"\u274C failure (fix retired)"}`,`**Resolved by:** ${c}`,"",`- CKG \`fixes\` edges decayed: **${o}**`,`- Losing lesson archived: **${p?"yes":"no"}**`,"",t==="failure"?"_The contradicted fix is retired. smart_recall will stop surfacing it; future attempts start fresh._":"_The fix is reaffirmed. The contradicting failure no longer blocks recall._"].join(`
|
|
34
|
+
`)}case"brain_confirm_ci":{const{instance_id:a,job_status:n,topics:t,scan_topics:c=[],source:s="manual"}=y;if(!n||!["success","failure","cancelled"].includes(n))throw new Error("job_status must be success, failure, or cancelled");if(!t?.length)throw new Error("topics is required");if(n==="cancelled")return"\u23ED\uFE0F CI was cancelled \u2014 no confidence changes applied.";let f;try{f=await b(`/api/v1/instances/${a}/ci-outcome`,{method:"POST",body:JSON.stringify({job_status:n,topics:t,scan_topics:c,source:s})})}catch{return"\u26A0\uFE0F Could not reach Brain API \u2014 CI outcome not recorded. Will retry on next call."}if(f.updated===0)return[`\u{1F916} **brain_confirm_ci** \u2014 CI ${n}`,"","No confidence changes (no matching predictions for these topics).",`Topics checked: ${t.slice(0,5).map(r=>`\`${r}\``).join(", ")}${t.length>5?` +${t.length-5} more`:""}`].join(`
|
|
35
|
+
`);const l=[`\u{1F916} **brain_confirm_ci** \u2014 CI ${n==="success"?"\u2705":"\u274C"} \xB7 ${f.updated} lesson${f.updated!==1?"s":""} updated`,"","| Topic | Old | New | \u0394 | Reason |","|---|---|---|---|---|"];for(const r of f.confidence_deltas){const o=r.delta>0?"+":"",p=r.reason==="confirmed_failure"?"\u{1F534}":"\u{1F7E1}";l.push(`| \`${r.topic}\` | ${(r.old*100).toFixed(0)}% | ${(r.new*100).toFixed(0)}% | ${o}${(r.delta*100).toFixed(0)}% | ${p} ${r.reason} |`)}return l.push("","_Brain self-calibrated from real CI signal. No manual `learn_from_attempts` needed._"),l.join(`
|
|
36
|
+
`)}case"brain_briefing":{const{instance_id:a,event_type:n,context:t,threshold:c}=y;if(!["file_open","pr_open","deploy","manual"].includes(n))throw new Error("event_type must be file_open, pr_open, deploy, or manual");if(!t?.trim())throw new Error("context is required");let s;try{s=await b(`/api/v1/instances/${a}/briefing`,{method:"POST",body:JSON.stringify({event_type:n,context:t,...c!==void 0?{threshold:c}:{}})})}catch{return"\u26A0\uFE0F Could not reach Brain API \u2014 no proactive briefing available right now."}const f=n==="file_open"?"\u{1F4C2}":n==="pr_open"?"\u{1F500}":n==="deploy"?"\u{1F680}":"\u{1F50D}";if(s.warnings.length===0)return[`${f} **brain_briefing** \u2014 ${n} \xB7 \u2705 no known risk patterns matched`,"",`Checked against ${s.matched_lessons} related lesson${s.matched_lessons!==1?"s":""}. You're clear to proceed.`].join(`
|
|
37
|
+
`);const l=s.risk_level==="high"?"\u{1F534}":s.risk_level==="medium"?"\u{1F7E1}":"\u{1F7E2}",r=[`${f} **brain_briefing** \u2014 ${n} \xB7 ${l} risk: ${s.risk_level.toUpperCase()}`,"",`The Brain proactively found ${s.warnings.length} known pattern${s.warnings.length!==1?"s":""} that may apply here:`,"","| Topic | Confidence | Severity | What to watch | Known fix |","|---|---|---|---|---|"];for(const o of s.warnings){const p=o.fix?o.fix.length>80?o.fix.slice(0,80)+"\u2026":o.fix:"\u2014";r.push(`| \`${o.topic}\` | ${(o.confidence*100).toFixed(0)}% | ${o.severity} | ${o.message} | ${p} |`)}return r.push("","_Surfaced proactively \u2014 no `smart_recall` needed. Address the highest-confidence items first._"),r.join(`
|
|
38
|
+
`)}default:return null}}export{A as SYNDICATE_TOOL_NAMES,W as handleSyndicateTool};
|