@cachly-dev/mcp-server 0.5.7 โ 0.5.9
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 +11 -0
- package/dist/index.js +348 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +362 -0
package/README.md
CHANGED
|
@@ -84,6 +84,17 @@ The MCP server gives your AI 30+ tools backed by your cachly Valkey instance:
|
|
|
84
84
|
| `cache_warmup` | Pre-warm the cache with prompt/response pairs |
|
|
85
85
|
| `detect_namespace` | Classify a prompt into semantic namespace |
|
|
86
86
|
|
|
87
|
+
### ๐ฅ Team Brain (v0.5.8+)
|
|
88
|
+
| Tool | What it does |
|
|
89
|
+
|------|-------------|
|
|
90
|
+
| `team_synthesize` | Groups lesson history by author/outcome โ proposes canonical merged lesson when 3+ devs stored the same fix differently |
|
|
91
|
+
| `list_orgs` | List your organizations and their plans |
|
|
92
|
+
| `create_org` | Create a new team org (name + plan) |
|
|
93
|
+
| `invite_member` | Invite a developer to your org by email |
|
|
94
|
+
| `get_org_plan` | Check org plan, seat usage, billing status |
|
|
95
|
+
|
|
96
|
+
> **How Team Brain works:** Every developer in your org uses the same `CACHLY_INSTANCE_ID`. Every `learn_from_attempts` call is visible to the whole team. `session_start` surfaces relevant lessons from any teammate before every session.
|
|
97
|
+
|
|
87
98
|
---
|
|
88
99
|
|
|
89
100
|
## Manual Setup (without wizard)
|
package/dist/index.js
CHANGED
|
@@ -1208,6 +1208,11 @@ const TOOLS = [
|
|
|
1208
1208
|
items: { type: 'string' },
|
|
1209
1209
|
description: 'Topic tags for filtering (e.g. ["bash", "deploy", "env"])',
|
|
1210
1210
|
},
|
|
1211
|
+
depends_on: {
|
|
1212
|
+
type: 'array',
|
|
1213
|
+
items: { type: 'string' },
|
|
1214
|
+
description: 'Prerequisites this lesson depends on (e.g. ["node:>=20", "docker:running", "wireguard:active"]). When a dependency is marked stale, all dependent lessons get needs_review.',
|
|
1215
|
+
},
|
|
1211
1216
|
},
|
|
1212
1217
|
required: ['instance_id', 'topic', 'outcome', 'what_worked'],
|
|
1213
1218
|
},
|
|
@@ -1428,6 +1433,21 @@ const TOOLS = [
|
|
|
1428
1433
|
required: ['instance_id'],
|
|
1429
1434
|
},
|
|
1430
1435
|
},
|
|
1436
|
+
{
|
|
1437
|
+
name: 'team_synthesize',
|
|
1438
|
+
description: 'Team Brain Synthesis โ merge multiple contributors\' lessons on the same topic into one canonical version. ' +
|
|
1439
|
+
'When 2+ developers store lessons for the same topic with different details, this proposes the best merged version. ' +
|
|
1440
|
+
'Shows: all contributions by author, what worked (consensus), what failed (union), canonical lesson to store. ' +
|
|
1441
|
+
'Use this when onboarding new team members or before documenting a process.',
|
|
1442
|
+
inputSchema: {
|
|
1443
|
+
type: 'object',
|
|
1444
|
+
properties: {
|
|
1445
|
+
instance_id: { type: 'string', description: 'UUID of the shared team brain instance' },
|
|
1446
|
+
topic: { type: 'string', description: 'Topic slug to synthesize (e.g. "deploy:api")' },
|
|
1447
|
+
},
|
|
1448
|
+
required: ['instance_id', 'topic'],
|
|
1449
|
+
},
|
|
1450
|
+
},
|
|
1431
1451
|
{
|
|
1432
1452
|
name: 'brain_doctor',
|
|
1433
1453
|
description: 'Check the health of your AI Brain and get actionable recommendations. ' +
|
|
@@ -1504,6 +1524,94 @@ const TOOLS = [
|
|
|
1504
1524
|
required: ['instance_id', 'framework'],
|
|
1505
1525
|
},
|
|
1506
1526
|
},
|
|
1527
|
+
// โโ Brain Archaeology + Causal Chain โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1528
|
+
{
|
|
1529
|
+
name: 'recall_at',
|
|
1530
|
+
description: 'Brain Archaeology โ see what a lesson looked like at a specific point in time. ' +
|
|
1531
|
+
'"What did we know about deployments 3 months ago?" ' +
|
|
1532
|
+
'Returns the history of a topic filtered to entries before the given date. ' +
|
|
1533
|
+
'Shows how the lesson evolved: failure โ partial โ success. ' +
|
|
1534
|
+
'Also useful to understand WHY old code decisions were made.',
|
|
1535
|
+
inputSchema: {
|
|
1536
|
+
type: 'object',
|
|
1537
|
+
properties: {
|
|
1538
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
1539
|
+
topic: { type: 'string', description: 'Topic slug to look up, e.g. "deploy:api"' },
|
|
1540
|
+
date: { type: 'string', description: 'ISO date string (e.g. "2026-01-15") โ returns entries stored BEFORE this date' },
|
|
1541
|
+
},
|
|
1542
|
+
required: ['instance_id', 'topic', 'date'],
|
|
1543
|
+
},
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
name: 'trace_dependency',
|
|
1547
|
+
description: 'Causal Chain โ find all lessons that depend on a given prerequisite. ' +
|
|
1548
|
+
'"What lessons are affected if node version changes?" ' +
|
|
1549
|
+
'When a dependency changes (new version, different provider, new OS), call this to see which lessons need review. ' +
|
|
1550
|
+
'Lessons store dependencies via the depends_on field in learn_from_attempts.',
|
|
1551
|
+
inputSchema: {
|
|
1552
|
+
type: 'object',
|
|
1553
|
+
properties: {
|
|
1554
|
+
instance_id: { type: 'string', description: 'UUID of the cache instance' },
|
|
1555
|
+
dependency: { type: 'string', description: 'Dependency to trace (e.g. "node:>=20", "docker:running", "wireguard:active")' },
|
|
1556
|
+
mark_review: { type: 'boolean', description: 'If true, marks all dependent lessons as needs_review (default: false)' },
|
|
1557
|
+
},
|
|
1558
|
+
required: ['instance_id', 'dependency'],
|
|
1559
|
+
},
|
|
1560
|
+
},
|
|
1561
|
+
// โโ Team / Org Management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1562
|
+
{
|
|
1563
|
+
name: 'list_orgs',
|
|
1564
|
+
description: 'List your Cachly organizations (team/org plans). ' +
|
|
1565
|
+
'Returns each org with plan, seat count, and member info. ' +
|
|
1566
|
+
'Org plans (Team โฌ99, Business โฌ299, Enterprise custom) are billed separately from cache tiers.',
|
|
1567
|
+
inputSchema: {
|
|
1568
|
+
type: 'object',
|
|
1569
|
+
properties: {},
|
|
1570
|
+
required: [],
|
|
1571
|
+
},
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
name: 'create_org',
|
|
1575
|
+
description: 'Create a new Cachly organization for team collaboration. ' +
|
|
1576
|
+
'After creation, invite team members with invite_member and upgrade the plan via the billing portal. ' +
|
|
1577
|
+
'Org plans: Team (โฌ99/mo, 10 seats), Business (โฌ299/mo, 50 seats), Enterprise (custom).',
|
|
1578
|
+
inputSchema: {
|
|
1579
|
+
type: 'object',
|
|
1580
|
+
properties: {
|
|
1581
|
+
name: { type: 'string', description: 'Organization display name (e.g. "Acme Engineering")' },
|
|
1582
|
+
slug: { type: 'string', description: 'URL-safe slug (e.g. "acme-eng"). Auto-generated from name if omitted.' },
|
|
1583
|
+
},
|
|
1584
|
+
required: ['name'],
|
|
1585
|
+
},
|
|
1586
|
+
},
|
|
1587
|
+
{
|
|
1588
|
+
name: 'invite_member',
|
|
1589
|
+
description: 'Invite a team member to a Cachly organization by email. ' +
|
|
1590
|
+
'They will receive an invite email and can join via the dashboard. ' +
|
|
1591
|
+
'Roles: owner (full access), admin (manage members + instances), member (read + cache ops).',
|
|
1592
|
+
inputSchema: {
|
|
1593
|
+
type: 'object',
|
|
1594
|
+
properties: {
|
|
1595
|
+
org_id: { type: 'string', description: 'UUID of the organization' },
|
|
1596
|
+
email: { type: 'string', description: 'Email address to invite' },
|
|
1597
|
+
role: { type: 'string', enum: ['admin', 'member'], description: 'Role for the invited member (default: member)' },
|
|
1598
|
+
},
|
|
1599
|
+
required: ['org_id', 'email'],
|
|
1600
|
+
},
|
|
1601
|
+
},
|
|
1602
|
+
{
|
|
1603
|
+
name: 'get_org_plan',
|
|
1604
|
+
description: 'Get the current org plan, seat usage, and billing info for an organization. ' +
|
|
1605
|
+
'Shows: plan name, price, seats used/max, next billing date. ' +
|
|
1606
|
+
'To upgrade: use the billing portal URL returned by this tool.',
|
|
1607
|
+
inputSchema: {
|
|
1608
|
+
type: 'object',
|
|
1609
|
+
properties: {
|
|
1610
|
+
org_id: { type: 'string', description: 'UUID of the organization' },
|
|
1611
|
+
},
|
|
1612
|
+
required: ['org_id'],
|
|
1613
|
+
},
|
|
1614
|
+
},
|
|
1507
1615
|
// โโ Legacy / Setup โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1508
1616
|
{
|
|
1509
1617
|
name: 'setup_ai_memory',
|
|
@@ -1669,6 +1777,63 @@ async function handleTool(name, args) {
|
|
|
1669
1777
|
await apiFetch(`/api/v1/instances/${instance_id}`, { method: 'DELETE' });
|
|
1670
1778
|
return `โ
Instance \`${instance_id}\` has been deleted and all data removed.`;
|
|
1671
1779
|
}
|
|
1780
|
+
// โโ Org / Team management โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1781
|
+
case 'list_orgs': {
|
|
1782
|
+
const res = await apiFetch('/api/v1/orgs');
|
|
1783
|
+
const orgs = res.orgs ?? [];
|
|
1784
|
+
if (orgs.length === 0)
|
|
1785
|
+
return `๐ญ No organizations yet.\n\nCreate one with \`create_org(name="My Team")\`.\nOrg plans: Team โฌ99/mo (10 seats), Business โฌ299/mo (50 seats), Enterprise custom.`;
|
|
1786
|
+
return [
|
|
1787
|
+
`๐ข **Your organizations (${orgs.length})**\n`,
|
|
1788
|
+
...orgs.map(o => `โข **${o.name}** (\`${o.slug}\`) โ plan: ${o.plan} ยท seats: ${o.member_count ?? '?'}/${o.max_members}\n ID: \`${o.id}\``),
|
|
1789
|
+
`\n_Manage: \`get_org_plan\`, \`invite_member\`, dashboard โ /team_`,
|
|
1790
|
+
].join('\n');
|
|
1791
|
+
}
|
|
1792
|
+
case 'create_org': {
|
|
1793
|
+
const { name: orgName, slug } = args;
|
|
1794
|
+
const body = { name: orgName };
|
|
1795
|
+
if (slug)
|
|
1796
|
+
body.slug = slug;
|
|
1797
|
+
const res = await apiFetch('/api/v1/orgs', {
|
|
1798
|
+
method: 'POST',
|
|
1799
|
+
body: JSON.stringify(body),
|
|
1800
|
+
});
|
|
1801
|
+
return [
|
|
1802
|
+
`โ
**Organization created:** ${res.name}`,
|
|
1803
|
+
` ID: \`${res.id}\` ยท Slug: \`${res.slug}\` ยท Plan: ${res.plan}`,
|
|
1804
|
+
``,
|
|
1805
|
+
`**Next steps:**`,
|
|
1806
|
+
`1. Invite team members: \`invite_member(org_id="${res.id}", email="dev@example.com")\``,
|
|
1807
|
+
`2. Upgrade plan: open billing portal via dashboard โ /team`,
|
|
1808
|
+
` Team: โฌ99/mo (10 seats) ยท Business: โฌ299/mo (50 seats)`,
|
|
1809
|
+
].join('\n');
|
|
1810
|
+
}
|
|
1811
|
+
case 'invite_member': {
|
|
1812
|
+
const { org_id, email, role = 'member' } = args;
|
|
1813
|
+
await apiFetch(`/api/v1/orgs/${org_id}/members`, {
|
|
1814
|
+
method: 'POST',
|
|
1815
|
+
body: JSON.stringify({ email, role }),
|
|
1816
|
+
});
|
|
1817
|
+
return `โ
Invite sent to **${email}** as \`${role}\` in org \`${org_id}\`.\n\nThey will receive an email to join the organization.`;
|
|
1818
|
+
}
|
|
1819
|
+
case 'get_org_plan': {
|
|
1820
|
+
const { org_id } = args;
|
|
1821
|
+
const org = await apiFetch(`/api/v1/orgs/${org_id}`);
|
|
1822
|
+
const accepted = (org.members ?? []).filter(m => m.accepted_at).length;
|
|
1823
|
+
const pending = (org.members ?? []).filter(m => !m.accepted_at).length;
|
|
1824
|
+
const planPrice = { free: 'โฌ0', team: 'โฌ99/mo', business: 'โฌ299/mo', enterprise: 'custom' };
|
|
1825
|
+
return [
|
|
1826
|
+
`๐ข **${org.name}** โ Plan: **${org.plan}** (${planPrice[org.plan] ?? org.plan})`,
|
|
1827
|
+
` Seats: ${accepted} active + ${pending} pending / ${org.max_members} max`,
|
|
1828
|
+
``,
|
|
1829
|
+
`**Members:**`,
|
|
1830
|
+
...(org.members ?? []).map(m => ` โข ${m.invite_email} (${m.role})${m.accepted_at ? '' : ' โ pending'}`),
|
|
1831
|
+
``,
|
|
1832
|
+
org.stripe_customer_id
|
|
1833
|
+
? `๐ณ Billing: managed via Stripe. Upgrade/cancel: dashboard โ /billing`
|
|
1834
|
+
: `๐ณ No payment method yet. Upgrade: dashboard โ /billing โ Team Plans`,
|
|
1835
|
+
].join('\n');
|
|
1836
|
+
}
|
|
1672
1837
|
// โโ Live cache operations โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
1673
1838
|
case 'cache_get': {
|
|
1674
1839
|
const { instance_id, key } = args;
|
|
@@ -2253,7 +2418,7 @@ async function handleTool(name, args) {
|
|
|
2253
2418
|
return `๐๏ธ **Forgot ${deleted} context entries.**\n\nKeys: ${keys.map(k => `\`${k}\``).join(', ')}`;
|
|
2254
2419
|
}
|
|
2255
2420
|
case 'learn_from_attempts': {
|
|
2256
|
-
const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], } = args;
|
|
2421
|
+
const { instance_id, topic, outcome, what_worked, what_failed = '', context: ctx = '', severity = 'major', file_paths = [], commands = [], tags = [], depends_on = [], } = args;
|
|
2257
2422
|
const redis = await getConnection(instance_id);
|
|
2258
2423
|
const ts = new Date().toISOString();
|
|
2259
2424
|
// โโ Structured template hints โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
@@ -2299,6 +2464,45 @@ async function handleTool(name, args) {
|
|
|
2299
2464
|
else {
|
|
2300
2465
|
auditTrail = [{ ts, action: 'created' }];
|
|
2301
2466
|
}
|
|
2467
|
+
// โโ "I Was Wrong" Protocol โ failure attribution โโโโโโโโโโโโโโโโโโโโโโโ
|
|
2468
|
+
const iWasWrongWarning = [];
|
|
2469
|
+
if (outcome === 'failure') {
|
|
2470
|
+
// Search for related success lessons that might have prevented this failure
|
|
2471
|
+
const scanKeys = [];
|
|
2472
|
+
const scanStream = redis.scanStream({ match: 'cachly:lesson:best:*', count: 100 });
|
|
2473
|
+
await new Promise((res, rej) => {
|
|
2474
|
+
scanStream.on('data', (b) => scanKeys.push(...b));
|
|
2475
|
+
scanStream.on('end', res);
|
|
2476
|
+
scanStream.on('error', rej);
|
|
2477
|
+
});
|
|
2478
|
+
const topicWords = topic.split(/[:\-_]/).filter(w => w.length > 2);
|
|
2479
|
+
for (const k of scanKeys.slice(0, 50)) {
|
|
2480
|
+
const raw = await redis.get(k);
|
|
2481
|
+
if (!raw)
|
|
2482
|
+
continue;
|
|
2483
|
+
try {
|
|
2484
|
+
const l = JSON.parse(raw);
|
|
2485
|
+
if (l.outcome !== 'success')
|
|
2486
|
+
continue;
|
|
2487
|
+
const lWords = (l.topic ?? '').split(/[:\-_]/).filter(w => w.length > 2);
|
|
2488
|
+
const overlap = topicWords.filter(w => lWords.includes(w)).length;
|
|
2489
|
+
if (overlap >= 1 && l.topic !== topic) {
|
|
2490
|
+
iWasWrongWarning.push(`โ ๏ธ **"I Was Wrong"**: lesson \`${l.topic}\` (success, ${l.severity ?? 'major'}) might have prevented this failure.`, ` โ Use \`recall_best_solution(topic="${l.topic}")\` before next attempt.`, ` โ To mark it critical: \`learn_from_attempts(topic="${l.topic}", ..., severity="critical")\``);
|
|
2491
|
+
break; // only show the most relevant match
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
catch { /* skip */ }
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
// โโ Register dependency index for causal chain โโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
2498
|
+
for (const dep of depends_on) {
|
|
2499
|
+
const depKey = `cachly:dep:${dep}`;
|
|
2500
|
+
const existing = await redis.get(depKey);
|
|
2501
|
+
const depTopics = existing ? JSON.parse(existing) : [];
|
|
2502
|
+
if (!depTopics.includes(topic))
|
|
2503
|
+
depTopics.push(topic);
|
|
2504
|
+
await redis.set(depKey, JSON.stringify(depTopics));
|
|
2505
|
+
}
|
|
2302
2506
|
const lessonObj = {
|
|
2303
2507
|
topic,
|
|
2304
2508
|
outcome,
|
|
@@ -2309,6 +2513,7 @@ async function handleTool(name, args) {
|
|
|
2309
2513
|
file_paths,
|
|
2310
2514
|
commands,
|
|
2311
2515
|
tags,
|
|
2516
|
+
depends_on,
|
|
2312
2517
|
recall_count: recallCount,
|
|
2313
2518
|
ts,
|
|
2314
2519
|
verified_at: outcome === 'success' || outcome === 'partial' ? ts : undefined,
|
|
@@ -2351,7 +2556,11 @@ async function handleTool(name, args) {
|
|
|
2351
2556
|
isUpdate
|
|
2352
2557
|
? `โป๏ธ Updated (recall count: ${recallCount} ยท audit entries: ${auditTrail.length})`
|
|
2353
2558
|
: `๐ก Recall later with \`recall_best_solution(topic="${topic}")\``,
|
|
2559
|
+
depends_on.length > 0
|
|
2560
|
+
? `๐ Depends on: ${depends_on.map(d => `\`${d}\``).join(', ')} โ trace with \`trace_dependency\``
|
|
2561
|
+
: '',
|
|
2354
2562
|
...templateWarnings,
|
|
2563
|
+
...iWasWrongWarning,
|
|
2355
2564
|
].filter(l => l !== '').join('\n');
|
|
2356
2565
|
}
|
|
2357
2566
|
case 'recall_best_solution': {
|
|
@@ -3140,6 +3349,64 @@ async function handleTool(name, args) {
|
|
|
3140
3349
|
}
|
|
3141
3350
|
return lines.join('\n');
|
|
3142
3351
|
}
|
|
3352
|
+
// โโ team_synthesize โ Team Brain Synthesis โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
3353
|
+
case 'team_synthesize': {
|
|
3354
|
+
const { instance_id, topic } = args;
|
|
3355
|
+
const redis = await getConnection(instance_id);
|
|
3356
|
+
// Load history list for this topic (all authors' contributions)
|
|
3357
|
+
const listKey = `cachly:lessons:${topic}`;
|
|
3358
|
+
const all = await redis.lrange(listKey, 0, -1);
|
|
3359
|
+
if (all.length < 2) {
|
|
3360
|
+
return `๐ญ Need at least 2 entries for topic \`${topic}\` to synthesize.\n\nCurrently: ${all.length} entr${all.length === 1 ? 'y' : 'ies'}.\n\nHave team members store lessons via \`learn_from_attempts(topic="${topic}", ...)\`.`;
|
|
3361
|
+
}
|
|
3362
|
+
const entries = all.map(r => { try {
|
|
3363
|
+
return JSON.parse(r);
|
|
3364
|
+
}
|
|
3365
|
+
catch {
|
|
3366
|
+
return null;
|
|
3367
|
+
} }).filter((e) => e !== null);
|
|
3368
|
+
// Group by outcome
|
|
3369
|
+
const successes = entries.filter(e => e.outcome === 'success');
|
|
3370
|
+
const failures = entries.filter(e => e.outcome === 'failure');
|
|
3371
|
+
const partials = entries.filter(e => e.outcome === 'partial');
|
|
3372
|
+
const authors = [...new Set(entries.map(e => e.author).filter(Boolean))];
|
|
3373
|
+
const hasMultiAuthor = authors.length > 1;
|
|
3374
|
+
// Build canonical merged version
|
|
3375
|
+
// what_worked: pick the most recent success, or longest for most detail
|
|
3376
|
+
const bestSuccess = successes.sort((a, b) => new Date(b.ts).getTime() - new Date(a.ts).getTime())[0];
|
|
3377
|
+
const whatWorkedCandidates = successes.map(e => e.what_worked).filter(w => w && w.length > 10);
|
|
3378
|
+
const canonicalWorked = whatWorkedCandidates.sort((a, b) => b.length - a.length)[0] ?? bestSuccess?.what_worked ?? '';
|
|
3379
|
+
// what_failed: union of all unique failure reasons
|
|
3380
|
+
const allFailed = [...new Set([...failures, ...partials].map(e => e.what_failed).filter((w) => !!w && w.length > 5))];
|
|
3381
|
+
const severities = entries.map(e => e.severity).filter(Boolean);
|
|
3382
|
+
const canonicalSeverity = severities.includes('critical') ? 'critical' : severities.includes('major') ? 'major' : 'minor';
|
|
3383
|
+
const lines = [
|
|
3384
|
+
`๐งฌ **Team Brain Synthesis: \`${topic}\`**`,
|
|
3385
|
+
`_${entries.length} entries from ${authors.length} author${authors.length === 1 ? '' : 's'}${hasMultiAuthor ? ` (${authors.join(', ')})` : ''} ยท ${successes.length} success ยท ${failures.length} failure ยท ${partials.length} partial_`,
|
|
3386
|
+
'',
|
|
3387
|
+
`**Canonical "what worked":**`,
|
|
3388
|
+
`> ${canonicalWorked}`,
|
|
3389
|
+
'',
|
|
3390
|
+
allFailed.length > 0 ? `**Avoid (combined failures):**` : '',
|
|
3391
|
+
...allFailed.map(f => `> โ ${f}`),
|
|
3392
|
+
allFailed.length > 0 ? '' : '',
|
|
3393
|
+
`**Suggested canonical lesson:**`,
|
|
3394
|
+
'```',
|
|
3395
|
+
`learn_from_attempts(`,
|
|
3396
|
+
` topic = "${topic}",`,
|
|
3397
|
+
` outcome = "success",`,
|
|
3398
|
+
` what_worked = "${canonicalWorked.replace(/"/g, "'")}",`,
|
|
3399
|
+
allFailed.length > 0 ? ` what_failed = "${allFailed[0].replace(/"/g, "'")}",` : '',
|
|
3400
|
+
` severity = "${canonicalSeverity}",`,
|
|
3401
|
+
`)`,
|
|
3402
|
+
'```',
|
|
3403
|
+
'',
|
|
3404
|
+
hasMultiAuthor
|
|
3405
|
+
? `๐ก _${authors.length} team members contributed to this synthesis. Store the canonical version to replace individual entries._`
|
|
3406
|
+
: `๐ก _Single author โ more value when multiple team members contribute to the same topic._`,
|
|
3407
|
+
].filter(l => l !== undefined).join('\n');
|
|
3408
|
+
return lines;
|
|
3409
|
+
}
|
|
3143
3410
|
// โโ brain_doctor โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
3144
3411
|
case 'brain_doctor': {
|
|
3145
3412
|
const { instance_id } = args;
|
|
@@ -3260,6 +3527,86 @@ async function handleTool(name, args) {
|
|
|
3260
3527
|
}
|
|
3261
3528
|
return lines.join('\n');
|
|
3262
3529
|
}
|
|
3530
|
+
// โโ recall_at โ Brain Archaeology โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
3531
|
+
case 'recall_at': {
|
|
3532
|
+
const { instance_id, topic, date } = args;
|
|
3533
|
+
const redis = await getConnection(instance_id);
|
|
3534
|
+
const cutoff = new Date(date).getTime();
|
|
3535
|
+
if (isNaN(cutoff))
|
|
3536
|
+
return `โ Invalid date "${date}". Use ISO format: "2026-01-15"`;
|
|
3537
|
+
const listKey = `cachly:lessons:${topic}`;
|
|
3538
|
+
const all = await redis.lrange(listKey, 0, -1);
|
|
3539
|
+
if (all.length === 0)
|
|
3540
|
+
return `๐ญ No history found for \`${topic}\`. Lessons are stored via \`learn_from_attempts\`.`;
|
|
3541
|
+
const before = all
|
|
3542
|
+
.map(raw => { try {
|
|
3543
|
+
return JSON.parse(raw);
|
|
3544
|
+
}
|
|
3545
|
+
catch {
|
|
3546
|
+
return null;
|
|
3547
|
+
} })
|
|
3548
|
+
.filter((l) => l !== null && new Date(l.ts).getTime() <= cutoff)
|
|
3549
|
+
.sort((a, b) => new Date(a.ts).getTime() - new Date(b.ts).getTime());
|
|
3550
|
+
if (before.length === 0)
|
|
3551
|
+
return `๐ญ No entries for \`${topic}\` found before **${date}**. Earliest entry: ${new Date(JSON.parse(all[0]).ts).toLocaleDateString('de-DE')}.`;
|
|
3552
|
+
const lines = [
|
|
3553
|
+
`๐บ **Brain Archaeology: \`${topic}\` before ${date}**`,
|
|
3554
|
+
`_${before.length} of ${all.length} total entries shown_`,
|
|
3555
|
+
'',
|
|
3556
|
+
];
|
|
3557
|
+
for (const l of before.slice(-10)) {
|
|
3558
|
+
const emoji = l.outcome === 'success' ? 'โ
' : l.outcome === 'partial' ? 'โ ๏ธ' : 'โ';
|
|
3559
|
+
const d = new Date(l.ts).toLocaleDateString('de-DE');
|
|
3560
|
+
lines.push(`**${d}** ${emoji} ${l.outcome}`);
|
|
3561
|
+
if (l.what_worked)
|
|
3562
|
+
lines.push(` โ ${l.what_worked.slice(0, 100)}`);
|
|
3563
|
+
lines.push('');
|
|
3564
|
+
}
|
|
3565
|
+
lines.push(`_Full evolution: ${all.map(r => { try {
|
|
3566
|
+
const l = JSON.parse(r);
|
|
3567
|
+
return l.outcome === 'success' ? 'โ
' : l.outcome === 'partial' ? 'โ ๏ธ' : 'โ';
|
|
3568
|
+
}
|
|
3569
|
+
catch {
|
|
3570
|
+
return '?';
|
|
3571
|
+
} }).join(' โ ')}_`);
|
|
3572
|
+
return lines.join('\n');
|
|
3573
|
+
}
|
|
3574
|
+
// โโ trace_dependency โ Causal Chain โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
3575
|
+
case 'trace_dependency': {
|
|
3576
|
+
const { instance_id, dependency, mark_review = false } = args;
|
|
3577
|
+
const redis = await getConnection(instance_id);
|
|
3578
|
+
const depKey = `cachly:dep:${dependency}`;
|
|
3579
|
+
const raw = await redis.get(depKey);
|
|
3580
|
+
if (!raw)
|
|
3581
|
+
return `๐ญ No lessons found that depend on \`${dependency}\`.\n\nAdd dependencies via: \`learn_from_attempts(..., depends_on=["${dependency}"])\``;
|
|
3582
|
+
const topics = JSON.parse(raw);
|
|
3583
|
+
const lines = [
|
|
3584
|
+
`๐ **Causal Chain: \`${dependency}\`** โ ${topics.length} dependent lesson${topics.length === 1 ? '' : 's'}`,
|
|
3585
|
+
'',
|
|
3586
|
+
];
|
|
3587
|
+
for (const t of topics) {
|
|
3588
|
+
const lessonRaw = await redis.get(`cachly:lesson:best:${t}`);
|
|
3589
|
+
if (!lessonRaw) {
|
|
3590
|
+
lines.push(` โข \`${t}\` _(lesson deleted)_`);
|
|
3591
|
+
continue;
|
|
3592
|
+
}
|
|
3593
|
+
const lesson = JSON.parse(lessonRaw);
|
|
3594
|
+
const emoji = lesson.outcome === 'success' ? 'โ
' : lesson.outcome === 'partial' ? 'โ ๏ธ' : 'โ';
|
|
3595
|
+
const reviewBadge = lesson.needs_review ? ' ๐ **needs_review**' : '';
|
|
3596
|
+
lines.push(` ${emoji} \`${t}\` (${lesson.severity ?? 'major'})${reviewBadge}`);
|
|
3597
|
+
if (mark_review) {
|
|
3598
|
+
const updated = { ...lesson, needs_review: true };
|
|
3599
|
+
await redis.set(`cachly:lesson:best:${t}`, JSON.stringify(updated));
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
if (mark_review) {
|
|
3603
|
+
lines.push('', `๐ All ${topics.length} lessons marked as **needs_review** โ verify they still work with the changed dependency.`);
|
|
3604
|
+
}
|
|
3605
|
+
else {
|
|
3606
|
+
lines.push('', `_Run with \`mark_review: true\` to flag all dependent lessons for re-verification._`);
|
|
3607
|
+
}
|
|
3608
|
+
return lines.join('\n');
|
|
3609
|
+
}
|
|
3263
3610
|
// โโ global_learn โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
3264
3611
|
case 'global_learn': {
|
|
3265
3612
|
const { instance_id, topic, lesson, severity = 'minor', tags = [] } = args;
|