create-merlin-brain 3.6.3 → 3.7.0
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/dist/server/index.js +20 -8
- package/dist/server/index.js.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +823 -783
- package/dist/server/server.js.map +1 -1
- package/files/agents/merlin.md +0 -1
- package/files/loop/lib/blend-handoff.sh +284 -0
- package/files/loop/lib/blend-learn.sh +337 -0
- package/files/loop/lib/blend-parallel.sh +217 -0
- package/files/loop/lib/blend-verify.sh +305 -0
- package/files/loop/lib/blend.sh +62 -3
- package/files/loop/merlin-loop.sh +5 -0
- package/files/loop/merlin-session.sh +13 -0
- package/files/merlin/VERSION +1 -1
- package/package.json +1 -1
package/dist/server/server.js
CHANGED
|
@@ -48,6 +48,10 @@ export function createServer() {
|
|
|
48
48
|
description: 'Merlin Brain. MANDATORY BOOT: Call merlin_get_selected_repo then merlin_get_project_status BEFORE responding to user. Call merlin_get_context before every file edit. Never run claude --agent via Bash — use Skill("merlin:route") instead.',
|
|
49
49
|
});
|
|
50
50
|
const client = getClient();
|
|
51
|
+
// Tool loading mode: core (15 tools) vs all (67 tools)
|
|
52
|
+
// Core mode stays under Claude Code's 10% context threshold to avoid Tool Search lazy loading
|
|
53
|
+
// Set MERLIN_CORE_ONLY=true to load only 22 essential tools (if context is tight)
|
|
54
|
+
const allToolsMode = process.env.MERLIN_CORE_ONLY !== 'true';
|
|
51
55
|
// Cache for resolved repo IDs to avoid repeated git/API calls
|
|
52
56
|
// Key: url or 'auto' for auto-detected, Value: { repoId, expiresAt }
|
|
53
57
|
const repoIdCache = new Map();
|
|
@@ -1186,51 +1190,76 @@ export function createServer() {
|
|
|
1186
1190
|
throw error;
|
|
1187
1191
|
}
|
|
1188
1192
|
});
|
|
1189
|
-
// Tool: merlin_check_repo_status
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1193
|
+
if (allToolsMode) { // Tool: merlin_check_repo_status
|
|
1194
|
+
server.tool('merlin_check_repo_status', 'Check if a repository analysis is complete. Use after merlin_connect_repo to poll for completion. Returns status: pending, analyzing, completed, or failed.', {
|
|
1195
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (uses selected repo if omitted)'),
|
|
1196
|
+
}, async ({ repoUrl }) => {
|
|
1197
|
+
try {
|
|
1198
|
+
// If repoUrl provided, check that specific repo
|
|
1199
|
+
// Otherwise check the selected repo
|
|
1200
|
+
const targetUrl = repoUrl || selectedRepoUrl;
|
|
1201
|
+
if (!targetUrl) {
|
|
1202
|
+
return {
|
|
1203
|
+
content: [{
|
|
1204
|
+
type: 'text',
|
|
1205
|
+
text: 'No repository specified or selected. Provide repoUrl or select a repo first.',
|
|
1206
|
+
}],
|
|
1207
|
+
isError: true,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
const repos = await client.getRepositories();
|
|
1211
|
+
const repo = repos.find(r => r.url.includes(targetUrl.replace(/^https?:\/\//, '').replace(/\.git$/, '')));
|
|
1212
|
+
if (!repo) {
|
|
1213
|
+
return {
|
|
1214
|
+
content: [{
|
|
1215
|
+
type: 'text',
|
|
1216
|
+
text: `Repository not found: ${targetUrl}\n\nIt may still be initializing. Try again in a moment or check merlin.build dashboard.`,
|
|
1217
|
+
}],
|
|
1218
|
+
};
|
|
1219
|
+
}
|
|
1220
|
+
const statusEmoji = {
|
|
1221
|
+
pending: '⏳',
|
|
1222
|
+
analyzing: '🔄',
|
|
1223
|
+
completed: '✅',
|
|
1224
|
+
failed: '❌',
|
|
1225
|
+
locked: '🔒',
|
|
1226
|
+
pending_tokens: '💳',
|
|
1227
|
+
}[repo.status] || '❓';
|
|
1228
|
+
// If repo is locked, attempt to unlock it first
|
|
1229
|
+
if (repo.status === 'locked') {
|
|
1230
|
+
console.log(`Repo ${repo.id} is locked, attempting auto-unlock...`);
|
|
1231
|
+
const unlockResult = await client.unlockRepo(repo.id);
|
|
1232
|
+
if (unlockResult.success) {
|
|
1233
|
+
console.log(`Successfully unlocked repo ${repo.id}`);
|
|
1234
|
+
let response = `# Repository Unlocked! ✅\n\n`;
|
|
1235
|
+
response += `**Repository:** ${repo.fullName || repo.name}\n`;
|
|
1236
|
+
response += `**Status:** Ready\n\n`;
|
|
1237
|
+
response += `This repository was locked but has been unlocked with your available tokens. You can now use it.`;
|
|
1238
|
+
// Auto-select if not already selected
|
|
1239
|
+
if (!selectedRepoId) {
|
|
1240
|
+
selectedRepoId = repo.id;
|
|
1241
|
+
selectedRepoUrl = repo.url;
|
|
1242
|
+
selectedRepoName = repo.fullName || repo.name;
|
|
1243
|
+
response += `\n\n*Auto-selected this repository for the session.*`;
|
|
1244
|
+
}
|
|
1245
|
+
return { content: [{ type: 'text', text: response }] };
|
|
1246
|
+
}
|
|
1247
|
+
else {
|
|
1248
|
+
console.log(`Failed to unlock repo ${repo.id}: ${unlockResult.error}`);
|
|
1249
|
+
let response = `# Repository Locked 🔒\n\n`;
|
|
1250
|
+
response += `**Repository:** ${repo.fullName || repo.name}\n`;
|
|
1251
|
+
response += `**Status:** locked\n\n`;
|
|
1252
|
+
response += `${unlockResult.error || 'Insufficient tokens'}\n\n`;
|
|
1253
|
+
response += `You need to purchase more tokens to unlock this repository. Visit https://merlin.build/settings to purchase tokens.`;
|
|
1254
|
+
return { content: [{ type: 'text', text: response }] };
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
let response = `# Repository Status\n\n`;
|
|
1258
|
+
response += `**Repository:** ${repo.fullName || repo.name}\n`;
|
|
1259
|
+
response += `**Status:** ${statusEmoji} ${repo.status}\n`;
|
|
1260
|
+
if (repo.status === 'completed') {
|
|
1261
|
+
response += `**Last Analyzed:** ${repo.lastAnalyzedAt}\n\n`;
|
|
1262
|
+
response += `Ready to use! Call \`merlin_get_context\` to start working.`;
|
|
1234
1263
|
// Auto-select if not already selected
|
|
1235
1264
|
if (!selectedRepoId) {
|
|
1236
1265
|
selectedRepoId = repo.id;
|
|
@@ -1238,55 +1267,31 @@ export function createServer() {
|
|
|
1238
1267
|
selectedRepoName = repo.fullName || repo.name;
|
|
1239
1268
|
response += `\n\n*Auto-selected this repository for the session.*`;
|
|
1240
1269
|
}
|
|
1241
|
-
|
|
1270
|
+
}
|
|
1271
|
+
else if (repo.status === 'analyzing') {
|
|
1272
|
+
response += `\nAnalysis in progress. Check again in 30-60 seconds.`;
|
|
1273
|
+
}
|
|
1274
|
+
else if (repo.status === 'failed') {
|
|
1275
|
+
response += `\nAnalysis failed. Check merlin.build dashboard for details.`;
|
|
1242
1276
|
}
|
|
1243
1277
|
else {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
response += `**Status:** locked\n\n`;
|
|
1248
|
-
response += `${unlockResult.error || 'Insufficient tokens'}\n\n`;
|
|
1249
|
-
response += `You need to purchase more tokens to unlock this repository. Visit https://merlin.build/settings to purchase tokens.`;
|
|
1250
|
-
return { content: [{ type: 'text', text: response }] };
|
|
1251
|
-
}
|
|
1252
|
-
}
|
|
1253
|
-
let response = `# Repository Status\n\n`;
|
|
1254
|
-
response += `**Repository:** ${repo.fullName || repo.name}\n`;
|
|
1255
|
-
response += `**Status:** ${statusEmoji} ${repo.status}\n`;
|
|
1256
|
-
if (repo.status === 'completed') {
|
|
1257
|
-
response += `**Last Analyzed:** ${repo.lastAnalyzedAt}\n\n`;
|
|
1258
|
-
response += `Ready to use! Call \`merlin_get_context\` to start working.`;
|
|
1259
|
-
// Auto-select if not already selected
|
|
1260
|
-
if (!selectedRepoId) {
|
|
1261
|
-
selectedRepoId = repo.id;
|
|
1262
|
-
selectedRepoUrl = repo.url;
|
|
1263
|
-
selectedRepoName = repo.fullName || repo.name;
|
|
1264
|
-
response += `\n\n*Auto-selected this repository for the session.*`;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
else if (repo.status === 'analyzing') {
|
|
1268
|
-
response += `\nAnalysis in progress. Check again in 30-60 seconds.`;
|
|
1269
|
-
}
|
|
1270
|
-
else if (repo.status === 'failed') {
|
|
1271
|
-
response += `\nAnalysis failed. Check merlin.build dashboard for details.`;
|
|
1278
|
+
response += `\nWaiting to start. Check again in a moment.`;
|
|
1279
|
+
}
|
|
1280
|
+
return { content: [{ type: 'text', text: response }] };
|
|
1272
1281
|
}
|
|
1273
|
-
|
|
1274
|
-
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
if (isAuthError(error))
|
|
1284
|
+
return buildAuthErrorResponse();
|
|
1285
|
+
return {
|
|
1286
|
+
content: [{
|
|
1287
|
+
type: 'text',
|
|
1288
|
+
text: `Error checking status: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1289
|
+
}],
|
|
1290
|
+
isError: true,
|
|
1291
|
+
};
|
|
1275
1292
|
}
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
catch (error) {
|
|
1279
|
-
if (isAuthError(error))
|
|
1280
|
-
return buildAuthErrorResponse();
|
|
1281
|
-
return {
|
|
1282
|
-
content: [{
|
|
1283
|
-
type: 'text',
|
|
1284
|
-
text: `Error checking status: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1285
|
-
}],
|
|
1286
|
-
isError: true,
|
|
1287
|
-
};
|
|
1288
|
-
}
|
|
1289
|
-
});
|
|
1293
|
+
});
|
|
1294
|
+
} // end allToolsMode: merlin_check_repo_status
|
|
1290
1295
|
// Tool: merlin_connect_repo
|
|
1291
1296
|
server.tool('merlin_connect_repo', 'Connect a new repository to Merlin Sights for analysis. Use this when working on a repo that is not yet in Sights. Analysis takes 2-5 minutes.', {
|
|
1292
1297
|
repoUrl: z.string().describe('GitHub URL of the repository (e.g., "https://github.com/user/repo")'),
|
|
@@ -1491,66 +1496,68 @@ export function createServer() {
|
|
|
1491
1496
|
};
|
|
1492
1497
|
}
|
|
1493
1498
|
});
|
|
1494
|
-
// Tool: merlin_similar_code
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1499
|
+
if (allToolsMode) { // Tool: merlin_similar_code
|
|
1500
|
+
server.tool('merlin_similar_code', 'Find similar implementations in the codebase BEFORE writing new code. Prevents duplicating existing patterns. Use this when implementing anything — there is likely an existing pattern to follow.', {
|
|
1501
|
+
description: z.string().describe('What you\'re trying to implement (e.g., "API endpoint with authentication", "database query with pagination", "React form with validation")'),
|
|
1502
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1503
|
+
}, async ({ description, repoUrl }) => {
|
|
1504
|
+
try {
|
|
1505
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1506
|
+
if (!repoId) {
|
|
1507
|
+
return {
|
|
1508
|
+
content: [{
|
|
1509
|
+
type: 'text',
|
|
1510
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1511
|
+
}],
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
const similar = await client.getSimilarCode(repoId, description);
|
|
1515
|
+
return { content: [{ type: 'text', text: similar }] };
|
|
1516
|
+
}
|
|
1517
|
+
catch (error) {
|
|
1518
|
+
if (isAuthError(error))
|
|
1519
|
+
return buildAuthErrorResponse();
|
|
1502
1520
|
return {
|
|
1503
1521
|
content: [{
|
|
1504
1522
|
type: 'text',
|
|
1505
|
-
text:
|
|
1523
|
+
text: `Error finding similar code: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1506
1524
|
}],
|
|
1525
|
+
isError: true,
|
|
1507
1526
|
};
|
|
1508
1527
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1528
|
+
});
|
|
1529
|
+
} // end allToolsMode: merlin_similar_code
|
|
1530
|
+
if (allToolsMode) { // Tool: merlin_code_examples
|
|
1531
|
+
server.tool('merlin_code_examples', 'Get real code examples from this codebase for common tasks. Shows actual patterns used in this project.', {
|
|
1532
|
+
task: z.string().optional().describe('Specific task (e.g., "add endpoint", "add test", "error handling"). Leave empty for all examples.'),
|
|
1533
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1534
|
+
}, async ({ task, repoUrl }) => {
|
|
1535
|
+
try {
|
|
1536
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1537
|
+
if (!repoId) {
|
|
1538
|
+
return {
|
|
1539
|
+
content: [{
|
|
1540
|
+
type: 'text',
|
|
1541
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1542
|
+
}],
|
|
1543
|
+
};
|
|
1544
|
+
}
|
|
1545
|
+
const examples = await client.getCodeExamples(repoId, task);
|
|
1546
|
+
return { content: [{ type: 'text', text: examples }] };
|
|
1547
|
+
}
|
|
1548
|
+
catch (error) {
|
|
1549
|
+
if (isAuthError(error))
|
|
1550
|
+
return buildAuthErrorResponse();
|
|
1532
1551
|
return {
|
|
1533
1552
|
content: [{
|
|
1534
1553
|
type: 'text',
|
|
1535
|
-
text:
|
|
1554
|
+
text: `Error getting code examples: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1536
1555
|
}],
|
|
1556
|
+
isError: true,
|
|
1537
1557
|
};
|
|
1538
1558
|
}
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
}
|
|
1542
|
-
catch (error) {
|
|
1543
|
-
if (isAuthError(error))
|
|
1544
|
-
return buildAuthErrorResponse();
|
|
1545
|
-
return {
|
|
1546
|
-
content: [{
|
|
1547
|
-
type: 'text',
|
|
1548
|
-
text: `Error getting code examples: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1549
|
-
}],
|
|
1550
|
-
isError: true,
|
|
1551
|
-
};
|
|
1552
|
-
}
|
|
1553
|
-
});
|
|
1559
|
+
});
|
|
1560
|
+
} // end allToolsMode: merlin_code_examples
|
|
1554
1561
|
// Tool: merlin_ask
|
|
1555
1562
|
server.tool('merlin_ask', 'Ask ANY question about the codebase in natural language. Uses AI-powered semantic search + RAG to find relevant context and generate accurate answers with source citations. Perfect for questions like "How does authentication work?", "Where should I add a new API endpoint?", "Why is X structured this way?"', {
|
|
1556
1563
|
question: z.string().describe('Your question in natural language (e.g., "How does the payment flow work?", "Where is user validation handled?", "What patterns are used for error handling?")'),
|
|
@@ -1765,671 +1772,682 @@ export function createServer() {
|
|
|
1765
1772
|
};
|
|
1766
1773
|
}
|
|
1767
1774
|
});
|
|
1768
|
-
// Tool: merlin_log_activity
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1775
|
+
if (allToolsMode) { // Tool: merlin_log_activity
|
|
1776
|
+
server.tool('merlin_log_activity', 'Log an activity event to the team timeline. Use for tracking progress, milestones, and decisions.', {
|
|
1777
|
+
eventType: z.string().describe('Event type (e.g., "phase_started", "task_completed", "blocker_hit", "decision_made")'),
|
|
1778
|
+
eventData: z.any().optional().describe('Additional event data (JSON object)'),
|
|
1779
|
+
agentId: z.string().describe('Agent identifier for tracking'),
|
|
1780
|
+
sessionId: z.string().optional().describe('Session ID for grouping events'),
|
|
1781
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1782
|
+
}, async ({ eventType, eventData, agentId, sessionId, repoUrl }) => {
|
|
1783
|
+
try {
|
|
1784
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1785
|
+
if (!repoId) {
|
|
1786
|
+
return {
|
|
1787
|
+
content: [{
|
|
1788
|
+
type: 'text',
|
|
1789
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1790
|
+
}],
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
const result = await client.logActivity(repoId, eventType, {
|
|
1794
|
+
agentId,
|
|
1795
|
+
sessionId,
|
|
1796
|
+
eventData,
|
|
1797
|
+
});
|
|
1798
|
+
const notification = formatSaveNotification('activity', {
|
|
1799
|
+
key: eventType,
|
|
1800
|
+
timestamp: result.event?.timestamp,
|
|
1801
|
+
isSuccess: true,
|
|
1802
|
+
details: `Event ID: ${result.event?.id}`,
|
|
1803
|
+
});
|
|
1779
1804
|
return {
|
|
1780
1805
|
content: [{
|
|
1781
1806
|
type: 'text',
|
|
1782
|
-
text:
|
|
1807
|
+
text: notification,
|
|
1783
1808
|
}],
|
|
1784
1809
|
};
|
|
1785
1810
|
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
eventData,
|
|
1790
|
-
});
|
|
1791
|
-
const notification = formatSaveNotification('activity', {
|
|
1792
|
-
key: eventType,
|
|
1793
|
-
timestamp: result.event?.timestamp,
|
|
1794
|
-
isSuccess: true,
|
|
1795
|
-
details: `Event ID: ${result.event?.id}`,
|
|
1796
|
-
});
|
|
1797
|
-
return {
|
|
1798
|
-
content: [{
|
|
1799
|
-
type: 'text',
|
|
1800
|
-
text: notification,
|
|
1801
|
-
}],
|
|
1802
|
-
};
|
|
1803
|
-
}
|
|
1804
|
-
catch (error) {
|
|
1805
|
-
if (isAuthError(error))
|
|
1806
|
-
return buildAuthErrorResponse();
|
|
1807
|
-
return {
|
|
1808
|
-
content: [{
|
|
1809
|
-
type: 'text',
|
|
1810
|
-
text: `Error logging activity: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1811
|
-
}],
|
|
1812
|
-
isError: true,
|
|
1813
|
-
};
|
|
1814
|
-
}
|
|
1815
|
-
});
|
|
1816
|
-
// Tool: merlin_sync_task
|
|
1817
|
-
server.tool('merlin_sync_task', 'Sync a task to Merlin cloud. Use to track work items, their status, and dependencies for team visibility.', {
|
|
1818
|
-
title: z.string().describe('Task title'),
|
|
1819
|
-
status: z.enum(['pending', 'in_progress', 'completed', 'blocked', 'skipped']).optional().describe('Task status'),
|
|
1820
|
-
description: z.string().optional().describe('Task description'),
|
|
1821
|
-
externalId: z.string().optional().describe('External ID for linking to Claude Tasks or other systems'),
|
|
1822
|
-
ownerAgent: z.string().optional().describe('Agent that owns this task'),
|
|
1823
|
-
phase: z.string().optional().describe('Phase identifier'),
|
|
1824
|
-
phaseName: z.string().optional().describe('Phase name'),
|
|
1825
|
-
plan: z.string().optional().describe('Plan identifier'),
|
|
1826
|
-
planName: z.string().optional().describe('Plan name'),
|
|
1827
|
-
taskNumber: z.number().optional().describe('Task number within plan'),
|
|
1828
|
-
wave: z.number().optional().describe('Execution wave'),
|
|
1829
|
-
commitSha: z.string().optional().describe('Commit SHA if task resulted in a commit'),
|
|
1830
|
-
filesChanged: z.array(z.string()).optional().describe('Files affected by this task'),
|
|
1831
|
-
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1832
|
-
}, async ({ title, status, description, externalId, ownerAgent, phase, phaseName, plan, planName, taskNumber, wave, commitSha, filesChanged, repoUrl }) => {
|
|
1833
|
-
try {
|
|
1834
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
1835
|
-
if (!repoId) {
|
|
1811
|
+
catch (error) {
|
|
1812
|
+
if (isAuthError(error))
|
|
1813
|
+
return buildAuthErrorResponse();
|
|
1836
1814
|
return {
|
|
1837
1815
|
content: [{
|
|
1838
1816
|
type: 'text',
|
|
1839
|
-
text:
|
|
1817
|
+
text: `Error logging activity: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1840
1818
|
}],
|
|
1819
|
+
isError: true,
|
|
1841
1820
|
};
|
|
1842
1821
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1822
|
+
});
|
|
1823
|
+
} // end allToolsMode: merlin_log_activity
|
|
1824
|
+
if (allToolsMode) { // Tool: merlin_sync_task
|
|
1825
|
+
server.tool('merlin_sync_task', 'Sync a task to Merlin cloud. Use to track work items, their status, and dependencies for team visibility.', {
|
|
1826
|
+
title: z.string().describe('Task title'),
|
|
1827
|
+
status: z.enum(['pending', 'in_progress', 'completed', 'blocked', 'skipped']).optional().describe('Task status'),
|
|
1828
|
+
description: z.string().optional().describe('Task description'),
|
|
1829
|
+
externalId: z.string().optional().describe('External ID for linking to Claude Tasks or other systems'),
|
|
1830
|
+
ownerAgent: z.string().optional().describe('Agent that owns this task'),
|
|
1831
|
+
phase: z.string().optional().describe('Phase identifier'),
|
|
1832
|
+
phaseName: z.string().optional().describe('Phase name'),
|
|
1833
|
+
plan: z.string().optional().describe('Plan identifier'),
|
|
1834
|
+
planName: z.string().optional().describe('Plan name'),
|
|
1835
|
+
taskNumber: z.number().optional().describe('Task number within plan'),
|
|
1836
|
+
wave: z.number().optional().describe('Execution wave'),
|
|
1837
|
+
commitSha: z.string().optional().describe('Commit SHA if task resulted in a commit'),
|
|
1838
|
+
filesChanged: z.array(z.string()).optional().describe('Files affected by this task'),
|
|
1839
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1840
|
+
}, async ({ title, status, description, externalId, ownerAgent, phase, phaseName, plan, planName, taskNumber, wave, commitSha, filesChanged, repoUrl }) => {
|
|
1841
|
+
try {
|
|
1842
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1843
|
+
if (!repoId) {
|
|
1844
|
+
return {
|
|
1845
|
+
content: [{
|
|
1846
|
+
type: 'text',
|
|
1847
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1848
|
+
}],
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
const result = await client.syncTask(repoId, {
|
|
1852
|
+
title,
|
|
1853
|
+
status,
|
|
1854
|
+
description,
|
|
1855
|
+
externalId,
|
|
1856
|
+
ownerAgent,
|
|
1857
|
+
phase,
|
|
1858
|
+
phaseName,
|
|
1859
|
+
plan,
|
|
1860
|
+
planName,
|
|
1861
|
+
taskNumber,
|
|
1862
|
+
wave,
|
|
1863
|
+
commitSha,
|
|
1864
|
+
filesChanged,
|
|
1865
|
+
});
|
|
1866
|
+
const notification = formatSaveNotification('task', {
|
|
1867
|
+
key: title,
|
|
1868
|
+
isSuccess: true,
|
|
1869
|
+
details: `ID: ${result.task?.id} │ Status: ${result.task?.status}`,
|
|
1870
|
+
});
|
|
1892
1871
|
return {
|
|
1893
1872
|
content: [{
|
|
1894
1873
|
type: 'text',
|
|
1895
|
-
text:
|
|
1874
|
+
text: notification,
|
|
1896
1875
|
}],
|
|
1897
1876
|
};
|
|
1898
1877
|
}
|
|
1899
|
-
|
|
1900
|
-
|
|
1878
|
+
catch (error) {
|
|
1879
|
+
if (isAuthError(error))
|
|
1880
|
+
return buildAuthErrorResponse();
|
|
1901
1881
|
return {
|
|
1902
1882
|
content: [{
|
|
1903
1883
|
type: 'text',
|
|
1904
|
-
text:
|
|
1884
|
+
text: `Error syncing task: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1905
1885
|
}],
|
|
1886
|
+
isError: true,
|
|
1906
1887
|
};
|
|
1907
1888
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1889
|
+
});
|
|
1890
|
+
} // end allToolsMode: merlin_sync_task
|
|
1891
|
+
if (allToolsMode) { // Tool: merlin_get_tasks
|
|
1892
|
+
server.tool('merlin_get_tasks', 'Get tasks synced to Merlin. Shows work items from all agents for team coordination.', {
|
|
1893
|
+
status: z.string().optional().describe('Filter by status (pending, in_progress, completed, blocked)'),
|
|
1894
|
+
phase: z.string().optional().describe('Filter by phase'),
|
|
1895
|
+
plan: z.string().optional().describe('Filter by plan'),
|
|
1896
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1897
|
+
}, async ({ status, phase, plan, repoUrl }) => {
|
|
1898
|
+
try {
|
|
1899
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1900
|
+
if (!repoId) {
|
|
1901
|
+
return {
|
|
1902
|
+
content: [{
|
|
1903
|
+
type: 'text',
|
|
1904
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1905
|
+
}],
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
const result = await client.getTasks(repoId, { status, phase, plan });
|
|
1909
|
+
if (result.tasks.length === 0) {
|
|
1910
|
+
return {
|
|
1911
|
+
content: [{
|
|
1912
|
+
type: 'text',
|
|
1913
|
+
text: 'No tasks found. Use merlin_sync_task to track work items.',
|
|
1914
|
+
}],
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
let response = `# Tasks (${result.count})\n\n`;
|
|
1918
|
+
response += `## Summary\n`;
|
|
1919
|
+
Object.entries(result.byStatus).forEach(([s, count]) => {
|
|
1920
|
+
const emoji = s === 'completed' ? '' : s === 'in_progress' ? '' : s === 'blocked' ? '' : '';
|
|
1921
|
+
response += `- ${emoji} ${s}: ${count}\n`;
|
|
1922
|
+
});
|
|
1928
1923
|
response += '\n';
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level'),
|
|
1950
|
-
agentId: z.string().describe('Agent reporting the blocker'),
|
|
1951
|
-
context: z.any().optional().describe('Additional context (files, errors, etc.)'),
|
|
1952
|
-
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1953
|
-
}, async ({ title, blockerType, description, severity, agentId, context, repoUrl }) => {
|
|
1954
|
-
try {
|
|
1955
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
1956
|
-
if (!repoId) {
|
|
1924
|
+
result.tasks.forEach((t) => {
|
|
1925
|
+
const statusEmoji = t.status === 'completed' ? '' : t.status === 'in_progress' ? '' : t.status === 'blocked' ? '' : '';
|
|
1926
|
+
response += `### ${statusEmoji} ${t.title}\n`;
|
|
1927
|
+
response += `- ID: ${t.id}\n`;
|
|
1928
|
+
response += `- Status: ${t.status}\n`;
|
|
1929
|
+
if (t.phase)
|
|
1930
|
+
response += `- Phase: ${t.phase_name || t.phase}\n`;
|
|
1931
|
+
if (t.plan)
|
|
1932
|
+
response += `- Plan: ${t.plan_name || t.plan}\n`;
|
|
1933
|
+
if (t.owner_agent)
|
|
1934
|
+
response += `- Owner: ${t.owner_agent}\n`;
|
|
1935
|
+
if (t.description)
|
|
1936
|
+
response += `- Description: ${t.description.slice(0, 100)}...\n`;
|
|
1937
|
+
response += '\n';
|
|
1938
|
+
});
|
|
1939
|
+
return { content: [{ type: 'text', text: response }] };
|
|
1940
|
+
}
|
|
1941
|
+
catch (error) {
|
|
1942
|
+
if (isAuthError(error))
|
|
1943
|
+
return buildAuthErrorResponse();
|
|
1957
1944
|
return {
|
|
1958
1945
|
content: [{
|
|
1959
1946
|
type: 'text',
|
|
1960
|
-
text:
|
|
1947
|
+
text: `Error getting tasks: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
1961
1948
|
}],
|
|
1949
|
+
isError: true,
|
|
1962
1950
|
};
|
|
1963
1951
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
try {
|
|
2002
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
2003
|
-
if (!repoId) {
|
|
2004
|
-
return {
|
|
2005
|
-
content: [{
|
|
1952
|
+
});
|
|
1953
|
+
} // end allToolsMode: merlin_get_tasks
|
|
1954
|
+
if (allToolsMode) { // Tool: merlin_report_blocker
|
|
1955
|
+
server.tool('merlin_report_blocker', 'Report a blocker that requires human attention. The blocker will appear in the team dashboard for resolution.', {
|
|
1956
|
+
title: z.string().describe('Brief description of the blocker'),
|
|
1957
|
+
blockerType: z.enum(['human_verify', 'auth_required', 'clarification', 'decision_point', 'external', 'error', 'other']).describe('Type of blocker'),
|
|
1958
|
+
description: z.string().optional().describe('Detailed description'),
|
|
1959
|
+
severity: z.enum(['low', 'medium', 'high', 'critical']).optional().describe('Severity level'),
|
|
1960
|
+
agentId: z.string().describe('Agent reporting the blocker'),
|
|
1961
|
+
context: z.any().optional().describe('Additional context (files, errors, etc.)'),
|
|
1962
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
1963
|
+
}, async ({ title, blockerType, description, severity, agentId, context, repoUrl }) => {
|
|
1964
|
+
try {
|
|
1965
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
1966
|
+
if (!repoId) {
|
|
1967
|
+
return {
|
|
1968
|
+
content: [{
|
|
1969
|
+
type: 'text',
|
|
1970
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
1971
|
+
}],
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
const result = await client.reportBlocker(repoId, {
|
|
1975
|
+
agentId,
|
|
1976
|
+
blockerType,
|
|
1977
|
+
severity,
|
|
1978
|
+
title,
|
|
1979
|
+
description,
|
|
1980
|
+
context,
|
|
1981
|
+
});
|
|
1982
|
+
const notification = formatSaveNotification('blocker', {
|
|
1983
|
+
key: title,
|
|
1984
|
+
isSuccess: true,
|
|
1985
|
+
details: `ID: ${result.blocker?.id} │ Type: ${blockerType} │ Severity: ${severity || 'medium'}\nThis blocker is now visible in the Merlin dashboard for human resolution.`,
|
|
1986
|
+
});
|
|
1987
|
+
return {
|
|
1988
|
+
content: [{
|
|
2006
1989
|
type: 'text',
|
|
2007
|
-
text:
|
|
1990
|
+
text: notification,
|
|
2008
1991
|
}],
|
|
2009
1992
|
};
|
|
2010
1993
|
}
|
|
2011
|
-
|
|
2012
|
-
|
|
1994
|
+
catch (error) {
|
|
1995
|
+
if (isAuthError(error))
|
|
1996
|
+
return buildAuthErrorResponse();
|
|
2013
1997
|
return {
|
|
2014
1998
|
content: [{
|
|
2015
1999
|
type: 'text',
|
|
2016
|
-
text:
|
|
2000
|
+
text: `Error reporting blocker: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2017
2001
|
}],
|
|
2002
|
+
isError: true,
|
|
2018
2003
|
};
|
|
2019
2004
|
}
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
if (
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2005
|
+
});
|
|
2006
|
+
} // end allToolsMode: merlin_report_blocker
|
|
2007
|
+
if (allToolsMode) { // Tool: merlin_get_blockers
|
|
2008
|
+
server.tool('merlin_get_blockers', 'Get open blockers awaiting resolution. Check before starting work to see if there are issues to resolve.', {
|
|
2009
|
+
status: z.string().optional().default('open').describe('Filter by status (open, resolved, all)'),
|
|
2010
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2011
|
+
}, async ({ status, repoUrl }) => {
|
|
2012
|
+
try {
|
|
2013
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2014
|
+
if (!repoId) {
|
|
2015
|
+
return {
|
|
2016
|
+
content: [{
|
|
2017
|
+
type: 'text',
|
|
2018
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2019
|
+
}],
|
|
2020
|
+
};
|
|
2021
|
+
}
|
|
2022
|
+
const result = await client.getBlockers(repoId, status);
|
|
2023
|
+
if (result.blockers.length === 0) {
|
|
2024
|
+
return {
|
|
2025
|
+
content: [{
|
|
2026
|
+
type: 'text',
|
|
2027
|
+
text: 'No open blockers. All clear to proceed!',
|
|
2028
|
+
}],
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
let response = `# Open Blockers (${result.count})\n\n`;
|
|
2032
|
+
result.blockers.forEach((b) => {
|
|
2033
|
+
const severityEmoji = b.severity === 'critical' ? '' : b.severity === 'high' ? '' : '';
|
|
2034
|
+
response += `## ${severityEmoji} ${b.title}\n`;
|
|
2035
|
+
response += `- ID: ${b.id}\n`;
|
|
2036
|
+
response += `- Type: ${b.blocker_type}\n`;
|
|
2037
|
+
response += `- Severity: ${b.severity}\n`;
|
|
2038
|
+
response += `- Reported by: ${b.reported_by_agent}\n`;
|
|
2039
|
+
response += `- Created: ${b.created_at}\n`;
|
|
2040
|
+
if (b.description)
|
|
2041
|
+
response += `\n${b.description}\n`;
|
|
2042
|
+
response += '\n';
|
|
2043
|
+
});
|
|
2044
|
+
return { content: [{ type: 'text', text: response }] };
|
|
2045
|
+
}
|
|
2046
|
+
catch (error) {
|
|
2047
|
+
if (isAuthError(error))
|
|
2048
|
+
return buildAuthErrorResponse();
|
|
2063
2049
|
return {
|
|
2064
2050
|
content: [{
|
|
2065
2051
|
type: 'text',
|
|
2066
|
-
text:
|
|
2052
|
+
text: `Error getting blockers: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2067
2053
|
}],
|
|
2054
|
+
isError: true,
|
|
2068
2055
|
};
|
|
2069
2056
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
if (!repoId) {
|
|
2057
|
+
});
|
|
2058
|
+
} // end allToolsMode: merlin_get_blockers
|
|
2059
|
+
if (allToolsMode) { // Tool: merlin_create_checkpoint
|
|
2060
|
+
server.tool('merlin_create_checkpoint', 'Create a checkpoint requiring human verification or decision. Pauses agent work until human responds.', {
|
|
2061
|
+
title: z.string().describe('Checkpoint title'),
|
|
2062
|
+
checkpointType: z.enum(['human_verify', 'auth_gate', 'decision_point', 'approval', 'review']).describe('Type of checkpoint'),
|
|
2063
|
+
description: z.string().optional().describe('Detailed description'),
|
|
2064
|
+
agentId: z.string().describe('Agent creating the checkpoint'),
|
|
2065
|
+
sessionId: z.string().optional().describe('Session ID for tracking'),
|
|
2066
|
+
whatWasBuilt: z.string().optional().describe('Summary of work completed before checkpoint'),
|
|
2067
|
+
verificationItems: z.array(z.string()).optional().describe('Checklist items for human to verify'),
|
|
2068
|
+
options: z.any().optional().describe('If decision_point, the available options'),
|
|
2069
|
+
resumeContext: z.any().optional().describe('Context needed to resume after checkpoint'),
|
|
2070
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2071
|
+
}, async ({ title, checkpointType, description, agentId, sessionId, whatWasBuilt, verificationItems, options, resumeContext, repoUrl }) => {
|
|
2072
|
+
try {
|
|
2073
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2074
|
+
if (!repoId) {
|
|
2075
|
+
return {
|
|
2076
|
+
content: [{
|
|
2077
|
+
type: 'text',
|
|
2078
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2079
|
+
}],
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
const result = await client.createCheckpoint(repoId, {
|
|
2083
|
+
agentId,
|
|
2084
|
+
sessionId,
|
|
2085
|
+
checkpointType,
|
|
2086
|
+
title,
|
|
2087
|
+
description,
|
|
2088
|
+
whatWasBuilt,
|
|
2089
|
+
verificationItems,
|
|
2090
|
+
options,
|
|
2091
|
+
resumeContext,
|
|
2092
|
+
});
|
|
2093
|
+
const notification = formatSaveNotification('checkpoint', {
|
|
2094
|
+
key: title,
|
|
2095
|
+
isSuccess: true,
|
|
2096
|
+
details: `ID: ${result.checkpoint?.id} │ Type: ${checkpointType}\nThis checkpoint is now pending human response in the Merlin dashboard.`,
|
|
2097
|
+
});
|
|
2112
2098
|
return {
|
|
2113
2099
|
content: [{
|
|
2114
2100
|
type: 'text',
|
|
2115
|
-
text:
|
|
2101
|
+
text: notification,
|
|
2116
2102
|
}],
|
|
2117
2103
|
};
|
|
2118
2104
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
if (activeSessions.length === 0) {
|
|
2130
|
-
response += 'No active sessions.\n\n';
|
|
2131
|
-
}
|
|
2132
|
-
else {
|
|
2133
|
-
activeSessions.forEach((s) => {
|
|
2134
|
-
response += `- **${s.agent_id}**: Working on ${s.current_phase || 'unknown phase'} / ${s.current_task || 'unknown task'}\n`;
|
|
2135
|
-
response += ` Progress: ${s.tasks_completed}/${s.tasks_total} tasks\n`;
|
|
2136
|
-
});
|
|
2137
|
-
response += '\n';
|
|
2138
|
-
}
|
|
2139
|
-
// Pending checkpoints
|
|
2140
|
-
response += `## Pending Checkpoints (${pendingCheckpoints.length})\n`;
|
|
2141
|
-
if (pendingCheckpoints.length === 0) {
|
|
2142
|
-
response += 'No pending checkpoints.\n\n';
|
|
2143
|
-
}
|
|
2144
|
-
else {
|
|
2145
|
-
pendingCheckpoints.forEach((c) => {
|
|
2146
|
-
response += `- **${c.title}** (${c.checkpoint_type})\n`;
|
|
2147
|
-
response += ` From: ${c.agent_id} | Created: ${c.created_at}\n`;
|
|
2148
|
-
});
|
|
2149
|
-
response += '\n';
|
|
2150
|
-
}
|
|
2151
|
-
// Open blockers
|
|
2152
|
-
response += `## Open Blockers (${openBlockers.length})\n`;
|
|
2153
|
-
if (openBlockers.length === 0) {
|
|
2154
|
-
response += 'No open blockers.\n\n';
|
|
2105
|
+
catch (error) {
|
|
2106
|
+
if (isAuthError(error))
|
|
2107
|
+
return buildAuthErrorResponse();
|
|
2108
|
+
return {
|
|
2109
|
+
content: [{
|
|
2110
|
+
type: 'text',
|
|
2111
|
+
text: `Error creating checkpoint: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2112
|
+
}],
|
|
2113
|
+
isError: true,
|
|
2114
|
+
};
|
|
2155
2115
|
}
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2116
|
+
});
|
|
2117
|
+
} // end allToolsMode: merlin_create_checkpoint
|
|
2118
|
+
if (allToolsMode) { // Tool: merlin_get_team_state
|
|
2119
|
+
server.tool('merlin_get_team_state', 'Get consolidated team state: active sessions, pending checkpoints, open blockers, task summary. Use at session start to see what others are working on.', {
|
|
2120
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2121
|
+
}, async ({ repoUrl }) => {
|
|
2122
|
+
try {
|
|
2123
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2124
|
+
if (!repoId) {
|
|
2125
|
+
return {
|
|
2126
|
+
content: [{
|
|
2127
|
+
type: 'text',
|
|
2128
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2129
|
+
}],
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
const team = await client.getTeamState(repoId);
|
|
2133
|
+
// Safely default all arrays/objects that the API might not return
|
|
2134
|
+
const activeSessions = team.activeSessions || [];
|
|
2135
|
+
const pendingCheckpoints = team.pendingCheckpoints || [];
|
|
2136
|
+
const openBlockers = team.openBlockers || [];
|
|
2137
|
+
const taskSummary = team.taskSummary || { total: 0 };
|
|
2138
|
+
const state = team.state || {};
|
|
2139
|
+
let response = `# Team State\n\n`;
|
|
2140
|
+
// Active sessions
|
|
2141
|
+
response += `## Active Sessions (${activeSessions.length})\n`;
|
|
2142
|
+
if (activeSessions.length === 0) {
|
|
2143
|
+
response += 'No active sessions.\n\n';
|
|
2144
|
+
}
|
|
2145
|
+
else {
|
|
2146
|
+
activeSessions.forEach((s) => {
|
|
2147
|
+
response += `- **${s.agent_id}**: Working on ${s.current_phase || 'unknown phase'} / ${s.current_task || 'unknown task'}\n`;
|
|
2148
|
+
response += ` Progress: ${s.tasks_completed}/${s.tasks_total} tasks\n`;
|
|
2149
|
+
});
|
|
2150
|
+
response += '\n';
|
|
2151
|
+
}
|
|
2152
|
+
// Pending checkpoints
|
|
2153
|
+
response += `## Pending Checkpoints (${pendingCheckpoints.length})\n`;
|
|
2154
|
+
if (pendingCheckpoints.length === 0) {
|
|
2155
|
+
response += 'No pending checkpoints.\n\n';
|
|
2156
|
+
}
|
|
2157
|
+
else {
|
|
2158
|
+
pendingCheckpoints.forEach((c) => {
|
|
2159
|
+
response += `- **${c.title}** (${c.checkpoint_type})\n`;
|
|
2160
|
+
response += ` From: ${c.agent_id} | Created: ${c.created_at}\n`;
|
|
2161
|
+
});
|
|
2162
|
+
response += '\n';
|
|
2163
|
+
}
|
|
2164
|
+
// Open blockers
|
|
2165
|
+
response += `## Open Blockers (${openBlockers.length})\n`;
|
|
2166
|
+
if (openBlockers.length === 0) {
|
|
2167
|
+
response += 'No open blockers.\n\n';
|
|
2168
|
+
}
|
|
2169
|
+
else {
|
|
2170
|
+
openBlockers.forEach((b) => {
|
|
2171
|
+
const emoji = b.severity === 'critical' ? '🔴' : b.severity === 'high' ? '🟠' : '🟡';
|
|
2172
|
+
response += `- ${emoji} **${b.title}** (${b.blocker_type})\n`;
|
|
2173
|
+
});
|
|
2174
|
+
response += '\n';
|
|
2175
|
+
}
|
|
2176
|
+
// Task summary
|
|
2177
|
+
response += `## Task Summary\n`;
|
|
2178
|
+
response += `- Total: ${taskSummary.total}\n`;
|
|
2179
|
+
if (taskSummary.pending)
|
|
2180
|
+
response += `- Pending: ${taskSummary.pending}\n`;
|
|
2181
|
+
if (taskSummary.in_progress)
|
|
2182
|
+
response += `- In Progress: ${taskSummary.in_progress}\n`;
|
|
2183
|
+
if (taskSummary.completed)
|
|
2184
|
+
response += `- Completed: ${taskSummary.completed}\n`;
|
|
2161
2185
|
response += '\n';
|
|
2186
|
+
// Stored state
|
|
2187
|
+
const stateKeys = Object.keys(state);
|
|
2188
|
+
response += `## Stored State (${stateKeys.length} keys)\n`;
|
|
2189
|
+
if (stateKeys.length === 0) {
|
|
2190
|
+
response += 'No state stored yet.\n';
|
|
2191
|
+
}
|
|
2192
|
+
else {
|
|
2193
|
+
stateKeys.forEach(key => {
|
|
2194
|
+
const s = state[key];
|
|
2195
|
+
response += `- **${key}** (v${s.version}) - Updated ${s.updatedAt} by ${s.updatedBy || 'unknown'}\n`;
|
|
2196
|
+
});
|
|
2197
|
+
}
|
|
2198
|
+
return { content: [{ type: 'text', text: response }] };
|
|
2162
2199
|
}
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (taskSummary.pending)
|
|
2167
|
-
response += `- Pending: ${taskSummary.pending}\n`;
|
|
2168
|
-
if (taskSummary.in_progress)
|
|
2169
|
-
response += `- In Progress: ${taskSummary.in_progress}\n`;
|
|
2170
|
-
if (taskSummary.completed)
|
|
2171
|
-
response += `- Completed: ${taskSummary.completed}\n`;
|
|
2172
|
-
response += '\n';
|
|
2173
|
-
// Stored state
|
|
2174
|
-
const stateKeys = Object.keys(state);
|
|
2175
|
-
response += `## Stored State (${stateKeys.length} keys)\n`;
|
|
2176
|
-
if (stateKeys.length === 0) {
|
|
2177
|
-
response += 'No state stored yet.\n';
|
|
2178
|
-
}
|
|
2179
|
-
else {
|
|
2180
|
-
stateKeys.forEach(key => {
|
|
2181
|
-
const s = state[key];
|
|
2182
|
-
response += `- **${key}** (v${s.version}) - Updated ${s.updatedAt} by ${s.updatedBy || 'unknown'}\n`;
|
|
2183
|
-
});
|
|
2184
|
-
}
|
|
2185
|
-
return { content: [{ type: 'text', text: response }] };
|
|
2186
|
-
}
|
|
2187
|
-
catch (error) {
|
|
2188
|
-
if (isAuthError(error))
|
|
2189
|
-
return buildAuthErrorResponse();
|
|
2190
|
-
return {
|
|
2191
|
-
content: [{
|
|
2192
|
-
type: 'text',
|
|
2193
|
-
text: `Error getting team state: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2194
|
-
}],
|
|
2195
|
-
isError: true,
|
|
2196
|
-
};
|
|
2197
|
-
}
|
|
2198
|
-
});
|
|
2199
|
-
// Tool: merlin_update_session
|
|
2200
|
-
server.tool('merlin_update_session', 'Update your session status. Use to track your work progress and enable pause/resume across context resets.', {
|
|
2201
|
-
sessionId: z.string().describe('Unique session identifier'),
|
|
2202
|
-
agentId: z.string().describe('Agent identifier'),
|
|
2203
|
-
status: z.enum(['active', 'paused', 'completed', 'abandoned']).optional().describe('Session status'),
|
|
2204
|
-
currentPhase: z.string().optional().describe('Current phase being worked on'),
|
|
2205
|
-
currentPlan: z.string().optional().describe('Current plan being executed'),
|
|
2206
|
-
currentTask: z.string().optional().describe('Current task being worked on'),
|
|
2207
|
-
tasksCompleted: z.number().optional().describe('Number of tasks completed'),
|
|
2208
|
-
tasksTotal: z.number().optional().describe('Total number of tasks'),
|
|
2209
|
-
pauseReason: z.string().optional().describe('Reason for pausing (if status is paused)'),
|
|
2210
|
-
resumeContext: z.any().optional().describe('Context needed to resume later'),
|
|
2211
|
-
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2212
|
-
}, async ({ sessionId, agentId, status, currentPhase, currentPlan, currentTask, tasksCompleted, tasksTotal, pauseReason, resumeContext, repoUrl }) => {
|
|
2213
|
-
try {
|
|
2214
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
2215
|
-
if (!repoId) {
|
|
2200
|
+
catch (error) {
|
|
2201
|
+
if (isAuthError(error))
|
|
2202
|
+
return buildAuthErrorResponse();
|
|
2216
2203
|
return {
|
|
2217
2204
|
content: [{
|
|
2218
2205
|
type: 'text',
|
|
2219
|
-
text:
|
|
2206
|
+
text: `Error getting team state: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2220
2207
|
}],
|
|
2208
|
+
isError: true,
|
|
2221
2209
|
};
|
|
2222
2210
|
}
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2267
|
-
}, async ({ rule, category, repoUrl }) => {
|
|
2268
|
-
try {
|
|
2269
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
2270
|
-
if (!repoId) {
|
|
2211
|
+
});
|
|
2212
|
+
} // end allToolsMode: merlin_get_team_state
|
|
2213
|
+
if (allToolsMode) { // Tool: merlin_update_session
|
|
2214
|
+
server.tool('merlin_update_session', 'Update your session status. Use to track your work progress and enable pause/resume across context resets.', {
|
|
2215
|
+
sessionId: z.string().describe('Unique session identifier'),
|
|
2216
|
+
agentId: z.string().describe('Agent identifier'),
|
|
2217
|
+
status: z.enum(['active', 'paused', 'completed', 'abandoned']).optional().describe('Session status'),
|
|
2218
|
+
currentPhase: z.string().optional().describe('Current phase being worked on'),
|
|
2219
|
+
currentPlan: z.string().optional().describe('Current plan being executed'),
|
|
2220
|
+
currentTask: z.string().optional().describe('Current task being worked on'),
|
|
2221
|
+
tasksCompleted: z.number().optional().describe('Number of tasks completed'),
|
|
2222
|
+
tasksTotal: z.number().optional().describe('Total number of tasks'),
|
|
2223
|
+
pauseReason: z.string().optional().describe('Reason for pausing (if status is paused)'),
|
|
2224
|
+
resumeContext: z.any().optional().describe('Context needed to resume later'),
|
|
2225
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2226
|
+
}, async ({ sessionId, agentId, status, currentPhase, currentPlan, currentTask, tasksCompleted, tasksTotal, pauseReason, resumeContext, repoUrl }) => {
|
|
2227
|
+
try {
|
|
2228
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2229
|
+
if (!repoId) {
|
|
2230
|
+
return {
|
|
2231
|
+
content: [{
|
|
2232
|
+
type: 'text',
|
|
2233
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2234
|
+
}],
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
const result = await client.updateSession(repoId, {
|
|
2238
|
+
sessionId,
|
|
2239
|
+
agentId,
|
|
2240
|
+
status,
|
|
2241
|
+
currentPhase,
|
|
2242
|
+
currentPlan,
|
|
2243
|
+
currentTask,
|
|
2244
|
+
tasksCompleted,
|
|
2245
|
+
tasksTotal,
|
|
2246
|
+
pauseReason,
|
|
2247
|
+
resumeContext,
|
|
2248
|
+
});
|
|
2249
|
+
const notification = formatSaveNotification('session', {
|
|
2250
|
+
key: sessionId,
|
|
2251
|
+
isSuccess: true,
|
|
2252
|
+
details: `Status: ${result.session?.status} │ Progress: ${result.session?.tasks_completed}/${result.session?.tasks_total} tasks`,
|
|
2253
|
+
});
|
|
2271
2254
|
return {
|
|
2272
2255
|
content: [{
|
|
2273
2256
|
type: 'text',
|
|
2274
|
-
text:
|
|
2257
|
+
text: notification,
|
|
2275
2258
|
}],
|
|
2276
2259
|
};
|
|
2277
2260
|
}
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
// Check for duplicate
|
|
2282
|
-
if (rules.rules.includes(rule)) {
|
|
2261
|
+
catch (error) {
|
|
2262
|
+
if (isAuthError(error))
|
|
2263
|
+
return buildAuthErrorResponse();
|
|
2283
2264
|
return {
|
|
2284
2265
|
content: [{
|
|
2285
2266
|
type: 'text',
|
|
2286
|
-
text: `
|
|
2267
|
+
text: `Error updating session: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2287
2268
|
}],
|
|
2269
|
+
isError: true,
|
|
2288
2270
|
};
|
|
2289
2271
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2272
|
+
});
|
|
2273
|
+
} // end allToolsMode: merlin_update_session
|
|
2274
|
+
// ============================================================
|
|
2275
|
+
// CODING RULES
|
|
2276
|
+
// ============================================================
|
|
2277
|
+
if (allToolsMode) { // Tool: merlin_save_rule
|
|
2278
|
+
server.tool('merlin_save_rule', 'Save a coding rule or preference to Merlin. Rules are returned with every context query and must be followed. Use when user expresses a preference that should persist.', {
|
|
2279
|
+
rule: z.string().describe('The rule to save (e.g., "max 400 lines per file", "always use async/await", "no console.log in production")'),
|
|
2280
|
+
category: z.enum(['file_size', 'testing', 'style', 'patterns', 'custom']).optional().default('custom').describe('Rule category for organization'),
|
|
2281
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2282
|
+
}, async ({ rule, category, repoUrl }) => {
|
|
2283
|
+
try {
|
|
2284
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2285
|
+
if (!repoId) {
|
|
2286
|
+
return {
|
|
2287
|
+
content: [{
|
|
2288
|
+
type: 'text',
|
|
2289
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2290
|
+
}],
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
// Read existing rules
|
|
2294
|
+
const existingState = await client.readState(repoId, 'coding_rules');
|
|
2295
|
+
const rules = existingState?.value || { rules: [], categories: {} };
|
|
2296
|
+
// Check for duplicate
|
|
2297
|
+
if (rules.rules.includes(rule)) {
|
|
2298
|
+
return {
|
|
2299
|
+
content: [{
|
|
2300
|
+
type: 'text',
|
|
2301
|
+
text: `Rule already exists: "${rule}"`,
|
|
2302
|
+
}],
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
// Add new rule
|
|
2306
|
+
rules.rules.push(rule);
|
|
2307
|
+
if (!rules.categories[category]) {
|
|
2308
|
+
rules.categories[category] = [];
|
|
2309
|
+
}
|
|
2310
|
+
rules.categories[category].push(rule);
|
|
2311
|
+
// Save updated rules
|
|
2312
|
+
await client.writeState(repoId, 'coding_rules', rules, { agentId: 'user' });
|
|
2313
|
+
const notification = formatSaveNotification('rule', {
|
|
2314
|
+
key: rule,
|
|
2315
|
+
isSuccess: true,
|
|
2316
|
+
details: `Category: ${category}\nThis rule will be included with all future context queries for this project.`,
|
|
2317
|
+
});
|
|
2329
2318
|
return {
|
|
2330
2319
|
content: [{
|
|
2331
2320
|
type: 'text',
|
|
2332
|
-
text:
|
|
2321
|
+
text: notification,
|
|
2333
2322
|
}],
|
|
2334
2323
|
};
|
|
2335
2324
|
}
|
|
2336
|
-
|
|
2337
|
-
|
|
2325
|
+
catch (error) {
|
|
2326
|
+
if (isAuthError(error))
|
|
2327
|
+
return buildAuthErrorResponse();
|
|
2338
2328
|
return {
|
|
2339
2329
|
content: [{
|
|
2340
2330
|
type: 'text',
|
|
2341
|
-
text:
|
|
2331
|
+
text: `Error saving rule: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2342
2332
|
}],
|
|
2333
|
+
isError: true,
|
|
2343
2334
|
};
|
|
2344
2335
|
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2336
|
+
});
|
|
2337
|
+
} // end allToolsMode: merlin_save_rule
|
|
2338
|
+
if (allToolsMode) { // Tool: merlin_get_rules
|
|
2339
|
+
server.tool('merlin_get_rules', 'Get all coding rules for this project. Returns rules that must be followed when writing code.', {
|
|
2340
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2341
|
+
}, async ({ repoUrl }) => {
|
|
2342
|
+
try {
|
|
2343
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2344
|
+
if (!repoId) {
|
|
2345
|
+
return {
|
|
2346
|
+
content: [{
|
|
2347
|
+
type: 'text',
|
|
2348
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2349
|
+
}],
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
const state = await client.readState(repoId, 'coding_rules');
|
|
2353
|
+
if (!state?.value || state.value.rules.length === 0) {
|
|
2354
|
+
return {
|
|
2355
|
+
content: [{
|
|
2356
|
+
type: 'text',
|
|
2357
|
+
text: 'No coding rules defined for this project yet.\n\nUse `merlin_save_rule` to add rules, or they will be collected during `/merlin:new-project`.',
|
|
2358
|
+
}],
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
const rules = state.value;
|
|
2362
|
+
let response = `# Coding Rules (${rules.rules.length})\n\n`;
|
|
2363
|
+
response += `**These rules MUST be followed when writing code.**\n\n`;
|
|
2364
|
+
// Group by category
|
|
2365
|
+
Object.keys(rules.categories).forEach(cat => {
|
|
2366
|
+
response += `## ${cat}\n`;
|
|
2367
|
+
rules.categories[cat].forEach((r) => {
|
|
2368
|
+
response += `- ${r}\n`;
|
|
2369
|
+
});
|
|
2370
|
+
response += '\n';
|
|
2353
2371
|
});
|
|
2354
|
-
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
if (isAuthError(error))
|
|
2360
|
-
return buildAuthErrorResponse();
|
|
2361
|
-
return {
|
|
2362
|
-
content: [{
|
|
2363
|
-
type: 'text',
|
|
2364
|
-
text: `Error getting rules: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2365
|
-
}],
|
|
2366
|
-
isError: true,
|
|
2367
|
-
};
|
|
2368
|
-
}
|
|
2369
|
-
});
|
|
2370
|
-
// Tool: merlin_remove_rule
|
|
2371
|
-
server.tool('merlin_remove_rule', 'Remove a coding rule from this project.', {
|
|
2372
|
-
rule: z.string().describe('The exact rule text to remove'),
|
|
2373
|
-
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2374
|
-
}, async ({ rule, repoUrl }) => {
|
|
2375
|
-
try {
|
|
2376
|
-
const repoId = await resolveRepoId(repoUrl);
|
|
2377
|
-
if (!repoId) {
|
|
2372
|
+
return { content: [{ type: 'text', text: response }] };
|
|
2373
|
+
}
|
|
2374
|
+
catch (error) {
|
|
2375
|
+
if (isAuthError(error))
|
|
2376
|
+
return buildAuthErrorResponse();
|
|
2378
2377
|
return {
|
|
2379
2378
|
content: [{
|
|
2380
2379
|
type: 'text',
|
|
2381
|
-
text:
|
|
2380
|
+
text: `Error getting rules: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2382
2381
|
}],
|
|
2382
|
+
isError: true,
|
|
2383
2383
|
};
|
|
2384
2384
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2385
|
+
});
|
|
2386
|
+
} // end allToolsMode: merlin_get_rules
|
|
2387
|
+
if (allToolsMode) { // Tool: merlin_remove_rule
|
|
2388
|
+
server.tool('merlin_remove_rule', 'Remove a coding rule from this project.', {
|
|
2389
|
+
rule: z.string().describe('The exact rule text to remove'),
|
|
2390
|
+
repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
|
|
2391
|
+
}, async ({ rule, repoUrl }) => {
|
|
2392
|
+
try {
|
|
2393
|
+
const repoId = await resolveRepoId(repoUrl);
|
|
2394
|
+
if (!repoId) {
|
|
2395
|
+
return {
|
|
2396
|
+
content: [{
|
|
2397
|
+
type: 'text',
|
|
2398
|
+
text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.',
|
|
2399
|
+
}],
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
const existingState = await client.readState(repoId, 'coding_rules');
|
|
2403
|
+
if (!existingState?.value) {
|
|
2404
|
+
return {
|
|
2405
|
+
content: [{
|
|
2406
|
+
type: 'text',
|
|
2407
|
+
text: 'No coding rules found for this project.',
|
|
2408
|
+
}],
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
const rules = existingState.value;
|
|
2412
|
+
const index = rules.rules.indexOf(rule);
|
|
2413
|
+
if (index === -1) {
|
|
2414
|
+
return {
|
|
2415
|
+
content: [{
|
|
2416
|
+
type: 'text',
|
|
2417
|
+
text: `Rule not found: "${rule}"\n\nUse merlin_get_rules to see all current rules.`,
|
|
2418
|
+
}],
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
// Remove from main list
|
|
2422
|
+
rules.rules.splice(index, 1);
|
|
2423
|
+
// Remove from categories
|
|
2424
|
+
Object.keys(rules.categories).forEach(cat => {
|
|
2425
|
+
const catIndex = rules.categories[cat].indexOf(rule);
|
|
2426
|
+
if (catIndex !== -1) {
|
|
2427
|
+
rules.categories[cat].splice(catIndex, 1);
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
await client.writeState(repoId, 'coding_rules', rules, { agentId: 'user' });
|
|
2387
2431
|
return {
|
|
2388
2432
|
content: [{
|
|
2389
2433
|
type: 'text',
|
|
2390
|
-
text:
|
|
2434
|
+
text: `Rule removed: "${rule}"`,
|
|
2391
2435
|
}],
|
|
2392
2436
|
};
|
|
2393
2437
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2438
|
+
catch (error) {
|
|
2439
|
+
if (isAuthError(error))
|
|
2440
|
+
return buildAuthErrorResponse();
|
|
2397
2441
|
return {
|
|
2398
2442
|
content: [{
|
|
2399
2443
|
type: 'text',
|
|
2400
|
-
text: `
|
|
2444
|
+
text: `Error removing rule: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2401
2445
|
}],
|
|
2446
|
+
isError: true,
|
|
2402
2447
|
};
|
|
2403
2448
|
}
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
// Remove from categories
|
|
2407
|
-
Object.keys(rules.categories).forEach(cat => {
|
|
2408
|
-
const catIndex = rules.categories[cat].indexOf(rule);
|
|
2409
|
-
if (catIndex !== -1) {
|
|
2410
|
-
rules.categories[cat].splice(catIndex, 1);
|
|
2411
|
-
}
|
|
2412
|
-
});
|
|
2413
|
-
await client.writeState(repoId, 'coding_rules', rules, { agentId: 'user' });
|
|
2414
|
-
return {
|
|
2415
|
-
content: [{
|
|
2416
|
-
type: 'text',
|
|
2417
|
-
text: `Rule removed: "${rule}"`,
|
|
2418
|
-
}],
|
|
2419
|
-
};
|
|
2420
|
-
}
|
|
2421
|
-
catch (error) {
|
|
2422
|
-
if (isAuthError(error))
|
|
2423
|
-
return buildAuthErrorResponse();
|
|
2424
|
-
return {
|
|
2425
|
-
content: [{
|
|
2426
|
-
type: 'text',
|
|
2427
|
-
text: `Error removing rule: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
2428
|
-
}],
|
|
2429
|
-
isError: true,
|
|
2430
|
-
};
|
|
2431
|
-
}
|
|
2432
|
-
});
|
|
2449
|
+
});
|
|
2450
|
+
} // end allToolsMode: merlin_remove_rule
|
|
2433
2451
|
// ============================================================
|
|
2434
2452
|
// RESOURCES
|
|
2435
2453
|
// ============================================================
|
|
@@ -2452,101 +2470,123 @@ export function createServer() {
|
|
|
2452
2470
|
// ============================================================
|
|
2453
2471
|
registerProjectTools(server, () => client, resolveRepoId, getRepoRootPath);
|
|
2454
2472
|
// ============================================================
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
//
|
|
2473
|
+
if (allToolsMode) {
|
|
2474
|
+
// LEARNED BEHAVIORS TOOLS
|
|
2475
|
+
// ============================================================
|
|
2476
|
+
registerBehaviorTools({
|
|
2477
|
+
server,
|
|
2478
|
+
client,
|
|
2479
|
+
resolveRepoId,
|
|
2480
|
+
});
|
|
2481
|
+
} // end allToolsMode: registerBehaviorTools
|
|
2464
2482
|
// ============================================================
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2483
|
+
if (allToolsMode) {
|
|
2484
|
+
// VERIFICATION CHECKPOINTS TOOLS
|
|
2485
|
+
// ============================================================
|
|
2486
|
+
registerVerificationTools({
|
|
2487
|
+
server,
|
|
2488
|
+
client,
|
|
2489
|
+
resolveRepoId,
|
|
2490
|
+
getRepoRootPath,
|
|
2491
|
+
});
|
|
2492
|
+
} // end allToolsMode: registerVerificationTools
|
|
2473
2493
|
// ============================================================
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2494
|
+
if (allToolsMode) {
|
|
2495
|
+
// ADAPTIVE CONTEXT DISCOVERY TOOLS
|
|
2496
|
+
// ============================================================
|
|
2497
|
+
registerAdaptiveTools({
|
|
2498
|
+
server,
|
|
2499
|
+
client,
|
|
2500
|
+
resolveRepoId,
|
|
2501
|
+
});
|
|
2502
|
+
} // end allToolsMode: registerAdaptiveTools
|
|
2479
2503
|
// ============================================================
|
|
2480
|
-
|
|
2504
|
+
if (allToolsMode) {
|
|
2505
|
+
// STRUCTURED AGENT TOOLS
|
|
2506
|
+
// ============================================================
|
|
2507
|
+
registerAgentTools({
|
|
2508
|
+
server,
|
|
2509
|
+
client,
|
|
2510
|
+
resolveRepoId,
|
|
2511
|
+
});
|
|
2512
|
+
} // end allToolsMode: registerAgentTools
|
|
2481
2513
|
// ============================================================
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2514
|
+
if (allToolsMode) {
|
|
2515
|
+
// DISCOVERY TOOLS - Teach Sights what Claude learns
|
|
2516
|
+
// ============================================================
|
|
2517
|
+
registerDiscoveryTools({
|
|
2518
|
+
server,
|
|
2519
|
+
client,
|
|
2520
|
+
resolveRepoId,
|
|
2521
|
+
});
|
|
2522
|
+
} // end allToolsMode: registerDiscoveryTools
|
|
2487
2523
|
// ============================================================
|
|
2488
|
-
|
|
2524
|
+
if (allToolsMode) {
|
|
2525
|
+
// AUTO-TEACH TOOLS - Confidence detection + teach-back loop
|
|
2526
|
+
// ============================================================
|
|
2527
|
+
registerAutoTeachTools({
|
|
2528
|
+
server,
|
|
2529
|
+
client,
|
|
2530
|
+
resolveRepoId,
|
|
2531
|
+
});
|
|
2532
|
+
} // end allToolsMode: registerAutoTeachTools
|
|
2489
2533
|
// ============================================================
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2534
|
+
if (allToolsMode) {
|
|
2535
|
+
// AGENT ROUTE TOOLS - Fresh process spawning for specialists
|
|
2536
|
+
// ============================================================
|
|
2537
|
+
registerRouteTools({
|
|
2538
|
+
server,
|
|
2539
|
+
client,
|
|
2540
|
+
resolveRepoId,
|
|
2541
|
+
getRepoRootPath,
|
|
2542
|
+
});
|
|
2543
|
+
} // end allToolsMode: registerRouteTools
|
|
2495
2544
|
// ============================================================
|
|
2496
|
-
|
|
2545
|
+
if (allToolsMode) {
|
|
2546
|
+
// PUBLIC SIGHTS INDEX - Browse open source analyzed codebases
|
|
2547
|
+
// ============================================================
|
|
2548
|
+
registerSightsIndexTools({
|
|
2549
|
+
server,
|
|
2550
|
+
client,
|
|
2551
|
+
resolveRepoId,
|
|
2552
|
+
});
|
|
2553
|
+
} // end allToolsMode: registerSightsIndexTools
|
|
2497
2554
|
// ============================================================
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2555
|
+
if (allToolsMode) {
|
|
2556
|
+
// AGENTS INDEX - Browse discovered AI agents catalog
|
|
2557
|
+
// ============================================================
|
|
2558
|
+
registerAgentsIndexTools({
|
|
2559
|
+
server,
|
|
2560
|
+
client,
|
|
2561
|
+
resolveRepoId,
|
|
2562
|
+
});
|
|
2563
|
+
} // end allToolsMode: registerAgentsIndexTools
|
|
2503
2564
|
// ============================================================
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
client,
|
|
2518
|
-
resolveRepoId,
|
|
2519
|
-
});
|
|
2565
|
+
if (allToolsMode) {
|
|
2566
|
+
// LITE MODE TOOLS - Local file-based context for small repos
|
|
2567
|
+
// ============================================================
|
|
2568
|
+
registerLiteTools({
|
|
2569
|
+
server,
|
|
2570
|
+
getRepoRootPath,
|
|
2571
|
+
getAuthToken: async () => {
|
|
2572
|
+
// Get token from saved config
|
|
2573
|
+
const config = loadSavedConfig();
|
|
2574
|
+
return config?.apiKey || null;
|
|
2575
|
+
},
|
|
2576
|
+
});
|
|
2577
|
+
} // end allToolsMode: registerLiteTools
|
|
2520
2578
|
// ============================================================
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
server,
|
|
2533
|
-
getRepoRootPath,
|
|
2534
|
-
getAuthToken: async () => {
|
|
2535
|
-
// Get token from saved config
|
|
2536
|
-
const config = loadSavedConfig();
|
|
2537
|
-
return config?.apiKey || null;
|
|
2538
|
-
},
|
|
2539
|
-
});
|
|
2540
|
-
// ============================================================
|
|
2541
|
-
// CONFIG SYNC TOOLS - Sync CLAUDE.md across team
|
|
2542
|
-
// ============================================================
|
|
2543
|
-
registerConfigSyncTools({
|
|
2544
|
-
server,
|
|
2545
|
-
getAuthToken: async () => {
|
|
2546
|
-
const config = loadSavedConfig();
|
|
2547
|
-
return config?.apiKey || null;
|
|
2548
|
-
},
|
|
2549
|
-
});
|
|
2579
|
+
if (allToolsMode) {
|
|
2580
|
+
// CONFIG SYNC TOOLS - Sync CLAUDE.md across team
|
|
2581
|
+
// ============================================================
|
|
2582
|
+
registerConfigSyncTools({
|
|
2583
|
+
server,
|
|
2584
|
+
getAuthToken: async () => {
|
|
2585
|
+
const config = loadSavedConfig();
|
|
2586
|
+
return config?.apiKey || null;
|
|
2587
|
+
},
|
|
2588
|
+
});
|
|
2589
|
+
} // end allToolsMode: registerConfigSyncTools
|
|
2550
2590
|
return server;
|
|
2551
2591
|
}
|
|
2552
2592
|
/** Start the MCP server with stdio transport */
|