create-merlin-brain 5.3.8 → 5.4.1

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.
Files changed (63) hide show
  1. package/bin/install-rtk.cjs +296 -0
  2. package/bin/install.cjs +9 -0
  3. package/dist/server/api/client.d.ts.map +1 -1
  4. package/dist/server/api/client.js +35 -6
  5. package/dist/server/api/client.js.map +1 -1
  6. package/dist/server/server.d.ts.map +1 -1
  7. package/dist/server/server.js +148 -42
  8. package/dist/server/server.js.map +1 -1
  9. package/dist/server/session-coach.d.ts.map +1 -1
  10. package/dist/server/session-coach.js +12 -0
  11. package/dist/server/session-coach.js.map +1 -1
  12. package/dist/server/session-guardian.d.ts +8 -1
  13. package/dist/server/session-guardian.d.ts.map +1 -1
  14. package/dist/server/session-guardian.js +26 -14
  15. package/dist/server/session-guardian.js.map +1 -1
  16. package/dist/server/tools/challenge.d.ts.map +1 -1
  17. package/dist/server/tools/challenge.js +7 -1
  18. package/dist/server/tools/challenge.js.map +1 -1
  19. package/dist/server/tools/computer-use.d.ts.map +1 -1
  20. package/dist/server/tools/computer-use.js +13 -6
  21. package/dist/server/tools/computer-use.js.map +1 -1
  22. package/dist/server/tools/index.d.ts +0 -1
  23. package/dist/server/tools/index.d.ts.map +1 -1
  24. package/dist/server/tools/index.js +0 -1
  25. package/dist/server/tools/index.js.map +1 -1
  26. package/dist/server/tools/project.d.ts.map +1 -1
  27. package/dist/server/tools/project.js +14 -12
  28. package/dist/server/tools/project.js.map +1 -1
  29. package/dist/server/tools/verification-runner.d.ts +45 -0
  30. package/dist/server/tools/verification-runner.d.ts.map +1 -0
  31. package/dist/server/tools/verification-runner.js +264 -0
  32. package/dist/server/tools/verification-runner.js.map +1 -0
  33. package/dist/server/tools/verification.d.ts +3 -0
  34. package/dist/server/tools/verification.d.ts.map +1 -1
  35. package/dist/server/tools/verification.js +8 -265
  36. package/dist/server/tools/verification.js.map +1 -1
  37. package/files/hooks/pre-edit-sights-check.sh +40 -3
  38. package/files/hooks/security-scanner.sh +3 -4
  39. package/files/hooks/session-end.sh +45 -32
  40. package/files/hooks/smart-approve.sh +11 -3
  41. package/files/hooks/user-prompt-router.sh +30 -3
  42. package/files/merlin/VERSION +1 -1
  43. package/package.json +2 -2
  44. package/dist/server/tools/context.d.ts +0 -7
  45. package/dist/server/tools/context.d.ts.map +0 -1
  46. package/dist/server/tools/context.js +0 -614
  47. package/dist/server/tools/context.js.map +0 -1
  48. package/dist/server/tools/hud.d.ts +0 -13
  49. package/dist/server/tools/hud.d.ts.map +0 -1
  50. package/dist/server/tools/hud.js +0 -295
  51. package/dist/server/tools/hud.js.map +0 -1
  52. package/dist/server/tools/provider-ask.d.ts +0 -10
  53. package/dist/server/tools/provider-ask.d.ts.map +0 -1
  54. package/dist/server/tools/provider-ask.js +0 -234
  55. package/dist/server/tools/provider-ask.js.map +0 -1
  56. package/dist/server/tools/rate-limit.d.ts +0 -8
  57. package/dist/server/tools/rate-limit.d.ts.map +0 -1
  58. package/dist/server/tools/rate-limit.js +0 -184
  59. package/dist/server/tools/rate-limit.js.map +0 -1
  60. package/dist/server/tools/team-workers.d.ts +0 -7
  61. package/dist/server/tools/team-workers.d.ts.map +0 -1
  62. package/dist/server/tools/team-workers.js +0 -271
  63. package/dist/server/tools/team-workers.js.map +0 -1
@@ -1,614 +0,0 @@
1
- /**
2
- * Context and Search Tools
3
- * Tools for getting codebase context, searching, and asking questions
4
- */
5
- import { z } from 'zod';
6
- import { writeFileSync, mkdirSync } from 'fs';
7
- import { join } from 'path';
8
- import { homedir } from 'os';
9
- import { detectLanguage } from '../lang/detector.js';
10
- import { getLanguageRules } from '../lang/rules.js';
11
- /**
12
- * Record that Sights was just consulted.
13
- * Writes epoch timestamp to ~/.claude/merlin/.last-sights-check
14
- * so that pre-edit hooks know context is fresh.
15
- * Also notifies the guardian HTTP sidecar if running.
16
- */
17
- function recordSightsCall() {
18
- try {
19
- const dir = join(homedir(), '.claude', 'merlin');
20
- mkdirSync(dir, { recursive: true });
21
- writeFileSync(join(dir, '.last-sights-check'), Math.floor(Date.now() / 1000).toString());
22
- }
23
- catch {
24
- // Non-fatal — hooks will fall back to stale detection
25
- }
26
- }
27
- /** Module-level cache: repoId → detected language profile */
28
- const langProfileCache = new Map();
29
- /** Detect language for a repo, using cached result if available */
30
- async function resolveLanguageProfile(repoId, client) {
31
- if (langProfileCache.has(repoId))
32
- return langProfileCache.get(repoId);
33
- try {
34
- const manifest = await client.getManifest(repoId).catch(() => null);
35
- if (!manifest)
36
- return null;
37
- const profile = detectLanguage(manifest);
38
- langProfileCache.set(repoId, profile);
39
- return profile;
40
- }
41
- catch {
42
- return null;
43
- }
44
- }
45
- export function registerContextTools(ctx) {
46
- const { server, client, resolveRepoId } = ctx;
47
- // ── CORE tool: always loaded, used at every session start ──────────────
48
- // Tool: merlin_get_brief
49
- server.tool('merlin_get_brief', 'CALL AFTER merlin_get_selected_repo to orient yourself. Returns ~500-token project brief: product purpose, tech stack, architecture pattern, service map, active tasks (in-progress/pending/completed counts), shipped APIs you must not duplicate, and top coding rules. Use at session start, before planning a new feature, or when joining an unfamiliar project.', {
50
- repoUrl: z.string().optional().describe('GitHub URL of the repository (uses selected repo if omitted)'),
51
- }, async ({ repoUrl }) => {
52
- try {
53
- const repoId = await resolveRepoId(repoUrl);
54
- if (!repoId) {
55
- return {
56
- content: [{ type: 'text', text: 'Could not find repository. Select a repo first with merlin_select_repo.' }],
57
- };
58
- }
59
- const [quickstart, projectState, rulesState, tasks, manifest] = await Promise.all([
60
- client.getQuickstart(repoId).catch(() => null),
61
- client.readState(repoId, 'project').catch(() => null),
62
- client.readState(repoId, 'coding_rules').catch(() => null),
63
- client.getTasks(repoId, {}).catch(() => ({ tasks: [], summary: {} })),
64
- client.getManifest(repoId).catch(() => null),
65
- ]);
66
- let brief = `# Project Brief\n\n`;
67
- // 1. PRODUCT SUMMARY
68
- brief += `## What We're Building\n`;
69
- if (projectState?.value) {
70
- const proj = projectState.value;
71
- brief += proj.oneLiner || proj.description || proj.name || 'See PROJECT.md';
72
- if (proj.targetUser)
73
- brief += `\nFor: ${proj.targetUser}`;
74
- if (proj.coreValue)
75
- brief += `\nValue: ${proj.coreValue}`;
76
- }
77
- else if (manifest) {
78
- const projName = manifest.project?.name || 'Unknown';
79
- const projDesc = manifest.project?.description || '';
80
- brief += `${projName}: ${projDesc}`;
81
- }
82
- else if (quickstart) {
83
- const lines = quickstart.split('\n').filter((l) => l.trim() && !l.startsWith('#'));
84
- brief += lines.slice(0, 2).join(' ');
85
- }
86
- else {
87
- brief += 'No product info stored. Run /merlin:new-project to set up.';
88
- }
89
- brief += '\n\n';
90
- // 2. ARCHITECTURE SUMMARY
91
- brief += `## How It's Built\n`;
92
- if (manifest) {
93
- const techStack = manifest.project?.techStack;
94
- brief += `Stack: ${Array.isArray(techStack) && techStack.length > 0 ? techStack.join(', ') : 'Not detected'}\n`;
95
- brief += `Architecture: ${manifest.project?.architecturePattern || 'Not detected'}\n`;
96
- const services = manifest.serviceMap;
97
- if (Array.isArray(services) && services.length > 0) {
98
- brief += `Services: ${services.map((m) => m.name).join(', ')}\n`;
99
- }
100
- }
101
- else if (quickstart) {
102
- brief += 'See quickstart for details.\n';
103
- }
104
- else {
105
- brief += 'Not analyzed yet.\n';
106
- }
107
- brief += '\n';
108
- // 3. STATUS
109
- brief += `## Status\n`;
110
- if (tasks.tasks && tasks.tasks.length > 0) {
111
- const completed = tasks.tasks.filter((t) => t.status === 'completed').length;
112
- const inProgress = tasks.tasks.filter((t) => t.status === 'in_progress').length;
113
- const pending = tasks.tasks.filter((t) => t.status === 'pending').length;
114
- brief += `Tasks: ${completed} done, ${inProgress} in progress, ${pending} pending\n`;
115
- const active = tasks.tasks.filter((t) => t.status === 'in_progress');
116
- if (active.length > 0) {
117
- brief += `Working on: ${active.map((t) => t.title).join(', ')}\n`;
118
- }
119
- }
120
- else {
121
- brief += 'No tasks tracked. Use /merlin:plan-phase to create tasks.\n';
122
- }
123
- brief += '\n';
124
- // 4. PRODUCT FEATURES (Skills, Shipped Assets)
125
- const productAssets = manifest?.productAssets || [];
126
- const shippedAssets = manifest?.shippedAssets || [];
127
- const discoveredFeatures = manifest?.discoveredFeatures || [];
128
- if (productAssets.length > 0 || discoveredFeatures.length > 0) {
129
- brief += `## Product Features\n`;
130
- // Show discovered features first
131
- if (discoveredFeatures.length > 0) {
132
- discoveredFeatures.slice(0, 5).forEach((f) => {
133
- brief += `- **${f.name}**: ${f.description}\n`;
134
- });
135
- }
136
- // Show skills/agents
137
- const skills = productAssets.filter((a) => a.type === 'skill' || a.type === 'agent');
138
- if (skills.length > 0) {
139
- brief += `\n**Skills/Agents (${skills.length}):** ${skills.map((s) => s.name).join(', ')}\n`;
140
- }
141
- brief += '\n';
142
- }
143
- // 5. WHAT USERS GET (Shipped Assets)
144
- if (shippedAssets.length > 0) {
145
- brief += `## What Users Get When They Install\n`;
146
- shippedAssets.forEach((pkg) => {
147
- brief += `**${pkg.package}** (${pkg.installCommand || 'npm install'}):\n`;
148
- pkg.assets?.slice(0, 5).forEach((a) => {
149
- brief += ` - ${a.name}: ${a.description?.slice(0, 50) || a.type}\n`;
150
- });
151
- });
152
- brief += '\n';
153
- }
154
- // 6. KEY APIS & SERVICES
155
- brief += `## Existing APIs & Services\n`;
156
- const entryPoints = manifest?.entryPoints;
157
- if (Array.isArray(entryPoints) && entryPoints.length > 0) {
158
- entryPoints.slice(0, 5).forEach((ep) => {
159
- brief += `- ${ep.name}: ${ep.purpose}\n`;
160
- });
161
- brief += `\nDO NOT duplicate these. Query Sights before adding new endpoints.\n`;
162
- }
163
- else {
164
- brief += 'Run merlin_get_context for specific areas.\n';
165
- }
166
- brief += '\n';
167
- // 7. RULES
168
- if (rulesState?.value && rulesState.value.rules?.length > 0) {
169
- brief += `## Your Rules\n`;
170
- rulesState.value.rules.slice(0, 5).forEach((r) => {
171
- brief += `- ${r}\n`;
172
- });
173
- brief += '\n';
174
- }
175
- brief += `---\n*Brief generated ${new Date().toISOString()}*`;
176
- return { content: [{ type: 'text', text: brief }] };
177
- }
178
- catch (error) {
179
- return {
180
- content: [{ type: 'text', text: `Error getting brief: ${error instanceof Error ? error.message : 'Unknown error'}` }],
181
- isError: true,
182
- };
183
- }
184
- });
185
- // ── CORE tool: MANDATORY before every file edit or code modification ────
186
- // Tool: merlin_get_context
187
- server.tool('merlin_get_context', 'MANDATORY before every file edit, code modification, or feature implementation. Call before EACH file you modify. Returns: codebase conventions, relevant file paths with exports, step-by-step how-to guide, anti-patterns to avoid, auto-apply behavior patterns, language-specific rules (TypeScript/Python/Go/etc.), and current project blockers. Without this you WILL duplicate existing code or violate established patterns. Re-call every few minutes — the codebase changes. Works in both cloud (Sights) and offline Lite mode.', {
188
- task: z.string().describe('What you want to know or do'),
189
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
190
- }, async ({ task, repoUrl }) => {
191
- try {
192
- const repoId = await resolveRepoId(repoUrl);
193
- if (!repoId) {
194
- return {
195
- content: [{ type: 'text', text: 'Could not find repository. Make sure:\n1. You are in a git repository\n2. The repository has been analyzed on merlin.build\n3. Or provide the repoUrl parameter explicitly' }],
196
- };
197
- }
198
- // Fetch all context in parallel, including behaviors
199
- const [howTo, files, conventions, behaviorsResult] = await Promise.all([
200
- client.getHowTo(repoId, task),
201
- client.getFiles(repoId, { purpose: task }),
202
- client.getConventions(repoId),
203
- client.getRelevantBehaviors(repoId, task).catch(() => ({ behaviors: [], count: 0 })),
204
- ]);
205
- let context = `# Context for: ${task}\n\n`;
206
- // AUTO-INJECT: High-confidence behaviors (auto-apply patterns)
207
- const autoApplyBehaviors = behaviorsResult.behaviors.filter(b => b.autoApply);
208
- if (autoApplyBehaviors.length > 0) {
209
- context += `## 🎯 Auto-Apply Patterns (FOLLOW THESE)\n`;
210
- context += `_These patterns have been learned from your preferences. Apply them automatically._\n\n`;
211
- autoApplyBehaviors.forEach(b => {
212
- context += `**When:** ${b.pattern}\n`;
213
- context += `**Do:** ${b.action}\n`;
214
- context += `_Confidence: ${(b.confidence * 100).toFixed(0)}%_\n\n`;
215
- });
216
- }
217
- // Suggested behaviors (lower confidence)
218
- const suggestedBehaviors = behaviorsResult.behaviors.filter(b => !b.autoApply && b.confidence >= 0.4);
219
- if (suggestedBehaviors.length > 0) {
220
- context += `## 💡 Suggested Patterns (Consider These)\n`;
221
- suggestedBehaviors.slice(0, 3).forEach(b => {
222
- context += `- **${b.pattern}** → ${b.action}\n`;
223
- });
224
- context += '\n';
225
- }
226
- context += `## How To Guide\n${howTo}\n\n`;
227
- if (files.files.length > 0) {
228
- context += `## Relevant Files (${files.count} total)\n`;
229
- files.files.forEach(f => {
230
- context += `- **${f.path}**: ${f.purpose}\n`;
231
- if (f.exports.length > 0) {
232
- context += ` Exports: ${f.exports.map(e => e.name).join(', ')}\n`;
233
- }
234
- });
235
- context += '\n';
236
- }
237
- if (conventions.conventions.length > 0) {
238
- context += `## Conventions to Follow\n`;
239
- conventions.conventions.forEach(c => {
240
- context += `- **${c.category}**: ${c.rule}\n`;
241
- if (c.example)
242
- context += ` Example: ${c.example}\n`;
243
- });
244
- context += '\n';
245
- }
246
- if (conventions.antiPatterns.length > 0) {
247
- context += `## Anti-Patterns to Avoid\n`;
248
- conventions.antiPatterns.forEach(ap => {
249
- context += `- ❌ ${ap.pattern}\n → Instead: ${ap.instead}\n`;
250
- });
251
- }
252
- // LANGUAGE INTELLIGENCE: Inject language-specific rules
253
- try {
254
- const langProfile = await resolveLanguageProfile(repoId, client);
255
- if (langProfile && langProfile.primary !== 'unknown') {
256
- const langRules = getLanguageRules(langProfile);
257
- if (langRules) {
258
- const langLabel = langProfile.primary.charAt(0).toUpperCase() + langProfile.primary.slice(1);
259
- const confidence = langProfile.confidence >= 0.7 ? 'auto-detected' : 'inferred';
260
- context += `\n## 🗣️ Language: ${langLabel} (${confidence})\n`;
261
- context += langRules + '\n';
262
- }
263
- }
264
- }
265
- catch {
266
- // Silently skip if language detection fails
267
- }
268
- // AUTO-DETECT: Recommend approach based on task keywords
269
- const taskLower = task.toLowerCase();
270
- let recommendedApproach = '';
271
- if (taskLower.includes('review') || taskLower.includes('check') || taskLower.includes('audit')) {
272
- recommendedApproach = 'code-review';
273
- }
274
- else if (taskLower.includes('fix') || taskLower.includes('bug') || taskLower.includes('error') || taskLower.includes('debug')) {
275
- recommendedApproach = 'debugging';
276
- }
277
- else if (taskLower.includes('refactor') || taskLower.includes('clean') || taskLower.includes('organize')) {
278
- recommendedApproach = 'refactoring';
279
- }
280
- else if (taskLower.includes('test') || taskLower.includes('spec')) {
281
- recommendedApproach = 'testing';
282
- }
283
- else if (taskLower.includes('security') || taskLower.includes('auth') || taskLower.includes('permission')) {
284
- recommendedApproach = 'security-review';
285
- }
286
- else if (taskLower.includes('api') || taskLower.includes('endpoint') || taskLower.includes('route')) {
287
- recommendedApproach = 'api-design';
288
- }
289
- if (recommendedApproach) {
290
- context += `\n## 🤖 Recommended Approach: ${recommendedApproach}\n`;
291
- context += `_Based on your task, consider using a ${recommendedApproach} mindset._\n`;
292
- }
293
- // AUTO-INJECT: Relevant public Sights for reference
294
- try {
295
- const publicSights = await client.recommendSights(task, 3);
296
- if (publicSights.length > 0) {
297
- context += '\n## 📚 Related Open Source Sights\n';
298
- context += '_These public codebases have been analyzed and may contain useful patterns:_\n\n';
299
- publicSights.forEach(s => {
300
- context += `- **${s.owner}/${s.repo}** ⭐${s.stars.toLocaleString()} — ${s.description}\n`;
301
- context += ` → \`merlin_get_public_sight_context\` with owner="${s.owner}" repo="${s.repo}" to explore\n`;
302
- });
303
- context += '\n';
304
- }
305
- }
306
- catch {
307
- // Silently skip if public sights unavailable
308
- }
309
- // AUTO-INJECT: Current project state (tasks, blockers, plans)
310
- try {
311
- const teamState = await client.getTeamState(repoId);
312
- if (teamState) {
313
- const activeTaskCount = (teamState.taskSummary?.in_progress || 0) + (teamState.taskSummary?.pending || 0);
314
- const blockerCount = teamState.openBlockers?.length || 0;
315
- const stateKeys = Object.keys(teamState.state || {});
316
- if (activeTaskCount > 0 || blockerCount > 0 || stateKeys.length > 0) {
317
- context += '\n## 📋 Current Project State\n';
318
- context += '_Synced from your Merlin dashboard — DO NOT ignore this data._\n\n';
319
- if (activeTaskCount > 0) {
320
- context += `**Active Tasks:** ${activeTaskCount} (${teamState.taskSummary?.in_progress || 0} in progress, ${teamState.taskSummary?.pending || 0} pending)\n`;
321
- }
322
- if (blockerCount > 0) {
323
- context += `\n**⚠️ Blockers:** ${blockerCount} open blockers need attention\n`;
324
- (teamState.openBlockers || []).slice(0, 3).forEach((b) => {
325
- context += `- ${b.title || b.description} (${b.severity || 'medium'})\n`;
326
- });
327
- }
328
- const importantKeys = stateKeys.filter((k) => k.toLowerCase().includes('project') || k.toLowerCase().includes('roadmap') || k.toLowerCase().includes('todo'));
329
- if (importantKeys.length > 0) {
330
- context += `\n**Planning Artifacts:** ${importantKeys.join(', ')}\n`;
331
- }
332
- context += '\n';
333
- }
334
- }
335
- }
336
- catch {
337
- // Silently skip if team state unavailable
338
- }
339
- // ── Phase 9 Wave 4: Project Picture augmentation ─────────────────────
340
- // Gated by MERLIN_CROSS_BRANCH_ENABLED — always best-effort, never blocking
341
- if (process.env.MERLIN_CROSS_BRANCH_ENABLED === 'true') {
342
- try {
343
- let currentBranch = null;
344
- try {
345
- const { execFile } = await import('child_process');
346
- const { promisify } = await import('util');
347
- const execFileAsync = promisify(execFile);
348
- const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { timeout: 3000 });
349
- const b = stdout.trim();
350
- if (b && b !== 'HEAD')
351
- currentBranch = b;
352
- }
353
- catch { /* non-fatal */ }
354
- const apiBase = process.env.MERLIN_API_URL ?? 'https://auth.merlin.build';
355
- const ppUrl = new URL(`/api/repos/${repoId}/project-picture`, apiBase);
356
- if (currentBranch)
357
- ppUrl.searchParams.set('branch', currentBranch);
358
- if (task)
359
- ppUrl.searchParams.set('task', task.slice(0, 200));
360
- const ppHeaders = { 'Content-Type': 'application/json' };
361
- const ppKey = process.env.MERLIN_API_KEY;
362
- if (ppKey)
363
- ppHeaders['Authorization'] = `Bearer ${ppKey}`;
364
- const ppResp = await fetch(ppUrl.toString(), {
365
- headers: ppHeaders,
366
- signal: AbortSignal.timeout(5000),
367
- });
368
- if (ppResp.ok) {
369
- const pp = await ppResp.json();
370
- context += `\n## 🌿 Project Picture (cross-branch)\n`;
371
- context += `_${pp.disclaimer}_\n\n`;
372
- context += `**Current Branch:** \`${pp.currentBranch}\`\n`;
373
- if (pp.liveBranches && pp.liveBranches.length > 0) {
374
- context += `**Live Branches (${pp.liveBranches.length}):** ${pp.liveBranches.slice(0, 8).map((b) => `\`${b.name}\``).join(', ')}\n`;
375
- }
376
- else {
377
- context += `**Live Branches:** _(none — single-branch workspace)_\n`;
378
- }
379
- if (pp.signals && pp.signals.length > 0) {
380
- const topSignals = pp.signals.slice(0, 5);
381
- context += `\n**Signals (top ${topSignals.length}):**\n`;
382
- for (const s of topSignals) {
383
- const emoji = s.kind === 'drift' ? '⚠️' : '💡';
384
- context += `${emoji} **${s.kind}** \`${s.filePath}\` severity=${s.severity.toFixed(2)} — ${s.summary}\n`;
385
- context += ` _(${s.sourceBranch} → ${s.targetBranch})_\n`;
386
- }
387
- }
388
- else {
389
- context += `**Signals:** _(none — no cross-branch conflicts detected)_\n`;
390
- }
391
- context += '\n';
392
- }
393
- }
394
- catch {
395
- // Strictly best-effort — never break context delivery
396
- }
397
- }
398
- // ── End Project Picture augmentation ─────────────────────────────────
399
- // Bridge: update file-based timestamp so pre-edit hooks know Sights is fresh
400
- recordSightsCall();
401
- return { content: [{ type: 'text', text: context }] };
402
- }
403
- catch (error) {
404
- return {
405
- content: [{ type: 'text', text: `Error getting context: ${error instanceof Error ? error.message : 'Unknown error'}` }],
406
- isError: true,
407
- };
408
- }
409
- });
410
- // ── CORE tool: always loaded, used before reading or modifying any file ─
411
- // Tool: merlin_find_files
412
- server.tool('merlin_find_files', 'Find files by purpose, description, or architectural layer. Call BEFORE creating any new file to check if something similar already exists. Returns file paths, exports, dependencies, and which files to modify for a given task. Supports layer filters: routes, services, models, utils, components, hooks, middleware, config, tests. Always re-fetch before editing — files may have moved. Use to locate existing functions before adding duplicates.', {
413
- query: z.string().describe('What you are looking for'),
414
- layer: z.string().optional().describe('Filter by layer (routes, services, models, utils, components, etc.)'),
415
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
416
- }, async ({ query, layer, repoUrl }) => {
417
- try {
418
- const repoId = await resolveRepoId(repoUrl);
419
- if (!repoId) {
420
- return {
421
- content: [{ type: 'text', text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.' }],
422
- };
423
- }
424
- const files = await client.getFiles(repoId, { layer, purpose: query });
425
- if (files.files.length > 0) {
426
- let result = `# Files matching "${query}"${layer ? ` in ${layer} layer` : ''}\n\n`;
427
- result += `Found ${files.count} files:\n\n`;
428
- files.files.forEach(f => {
429
- result += `## ${f.path}\n`;
430
- result += `- **Purpose**: ${f.purpose}\n`;
431
- result += `- **Layer**: ${f.layer}\n`;
432
- if (f.exports.length > 0) {
433
- result += `- **Exports**: ${f.exports.map(e => `${e.name} (${e.type})`).join(', ')}\n`;
434
- }
435
- if (f.modifyFor.length > 0) {
436
- result += `- **Modify for**: ${f.modifyFor.join(', ')}\n`;
437
- }
438
- result += '\n';
439
- });
440
- recordSightsCall();
441
- return { content: [{ type: 'text', text: result }] };
442
- }
443
- const findResult = await client.findFiles(repoId, query);
444
- recordSightsCall();
445
- return { content: [{ type: 'text', text: findResult }] };
446
- }
447
- catch (error) {
448
- return {
449
- content: [{ type: 'text', text: `Error finding files: ${error instanceof Error ? error.message : 'Unknown error'}` }],
450
- isError: true,
451
- };
452
- }
453
- });
454
- // ── DEFERRED tool: loaded on-demand for style/rule questions ────────────
455
- // Tool: merlin_get_conventions
456
- server.tool('merlin_get_conventions', 'Get ALL coding conventions, anti-patterns, style rules, naming guidelines, and step-by-step change guides for the codebase. Returns rules organized by category (naming, structure, error handling, testing, etc.) with code examples and example files. Use when reviewing code quality, auditing for style violations, understanding what patterns to avoid, or learning the full rulebook before a major refactor.', {
457
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
458
- }, async ({ repoUrl }) => {
459
- try {
460
- const repoId = await resolveRepoId(repoUrl);
461
- if (!repoId) {
462
- return {
463
- content: [{ type: 'text', text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.' }],
464
- };
465
- }
466
- const conventions = await client.getConventions(repoId);
467
- let result = '# Coding Conventions\n\n';
468
- if (conventions.conventions.length > 0) {
469
- result += '## Conventions\n\n';
470
- conventions.conventions.forEach(c => {
471
- result += `### ${c.category}\n${c.rule}\n`;
472
- if (c.example)
473
- result += `\n**Example:**\n\`\`\`\n${c.example}\n\`\`\`\n`;
474
- if (c.exampleFiles.length > 0)
475
- result += `\n**See**: ${c.exampleFiles.join(', ')}\n`;
476
- result += '\n';
477
- });
478
- }
479
- if (conventions.antiPatterns.length > 0) {
480
- result += '## Anti-Patterns (Avoid These)\n\n';
481
- conventions.antiPatterns.forEach(ap => {
482
- result += `### ❌ ${ap.pattern}\n`;
483
- result += `**Reason**: ${ap.reason}\n**Instead**: ${ap.instead}\n**Severity**: ${ap.severity}\n\n`;
484
- });
485
- }
486
- if (conventions.changeGuides.length > 0) {
487
- result += '## Change Guides\n\n';
488
- conventions.changeGuides.slice(0, 5).forEach(cg => {
489
- result += `### ${cg.task}\n${cg.description}\n\n**Steps:**\n`;
490
- cg.steps.forEach(s => {
491
- result += `${s.order}. ${s.file}: ${s.description}\n`;
492
- });
493
- result += '\n';
494
- });
495
- }
496
- // LANGUAGE INTELLIGENCE: Append language-specific rules
497
- try {
498
- const langProfile = await resolveLanguageProfile(repoId, client);
499
- if (langProfile && langProfile.primary !== 'unknown') {
500
- const langRules = getLanguageRules(langProfile);
501
- if (langRules) {
502
- const langLabel = langProfile.primary.charAt(0).toUpperCase() + langProfile.primary.slice(1);
503
- const confidence = langProfile.confidence >= 0.7 ? 'auto-detected' : 'inferred';
504
- result += `## 🗣️ Language Rules: ${langLabel} (${confidence})\n\n`;
505
- result += langRules + '\n';
506
- }
507
- }
508
- }
509
- catch {
510
- // Silently skip if language detection fails
511
- }
512
- return { content: [{ type: 'text', text: result }] };
513
- }
514
- catch (error) {
515
- return {
516
- content: [{ type: 'text', text: `Error getting conventions: ${error instanceof Error ? error.message : 'Unknown error'}` }],
517
- isError: true,
518
- };
519
- }
520
- });
521
- // ── DEFERRED tool: loaded on-demand for onboarding/orientation ──────────
522
- // Tool: merlin_quickstart
523
- server.tool('merlin_quickstart', 'Get a 60-second onboarding guide for a new contributor or agent. Returns the project quickstart document: repo folder structure, entry points, development commands (install/build/test/run), environment setup, key concepts, and where to start. Use when first encountering an unfamiliar codebase, onboarding a new team member, or needing a fast orientation without reading the code.', {
524
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
525
- }, async ({ repoUrl }) => {
526
- try {
527
- const repoId = await resolveRepoId(repoUrl);
528
- if (!repoId) {
529
- return {
530
- content: [{ type: 'text', text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.' }],
531
- };
532
- }
533
- const quickstart = await client.getQuickstart(repoId);
534
- return { content: [{ type: 'text', text: quickstart }] };
535
- }
536
- catch (error) {
537
- return {
538
- content: [{ type: 'text', text: `Error getting quickstart: ${error instanceof Error ? error.message : 'Unknown error'}` }],
539
- isError: true,
540
- };
541
- }
542
- });
543
- // ── CORE tool: always loaded, used for searching documentation ──────────
544
- // Tool: merlin_search
545
- server.tool('merlin_search', 'Keyword search across project documentation, wiki, architecture notes, API docs, and knowledge base. Returns matching doc sections with real-time content from the latest commit. Use to find specific functions, API endpoints, configuration options, architecture decisions, or any documented behavior. Complement to merlin_get_context — use this for targeted keyword lookup when you know what you\'re searching for.', {
546
- query: z.string().describe('What you want to find'),
547
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
548
- }, async ({ query, repoUrl }) => {
549
- try {
550
- const repoId = await resolveRepoId(repoUrl);
551
- if (!repoId) {
552
- return {
553
- content: [{ type: 'text', text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.' }],
554
- };
555
- }
556
- const results = await client.search(repoId, query);
557
- recordSightsCall();
558
- return { content: [{ type: 'text', text: results }] };
559
- }
560
- catch (error) {
561
- return {
562
- content: [{ type: 'text', text: `Error searching: ${error instanceof Error ? error.message : 'Unknown error'}` }],
563
- isError: true,
564
- };
565
- }
566
- });
567
- // ── DEFERRED tool: loaded on-demand for natural language codebase Q&A ──
568
- // Tool: merlin_ask
569
- server.tool('merlin_ask', 'Ask a natural language question about the codebase and get an AI-generated answer with source citations. Uses semantic search + RAG across docs, code, conventions, and history. Use when you need a direct explanation rather than raw file paths — e.g., "how does authentication work?", "where is billing handled?", "what pattern is used for error handling?", "why does X exist?". Returns answer with high/medium relevance sources.', {
570
- question: z.string().describe('Your question in natural language'),
571
- quick: z.boolean().optional().default(false).describe('If true, returns a faster but shorter answer without detailed sources'),
572
- repoUrl: z.string().optional().describe('GitHub URL of the repository (auto-detected from git if omitted)'),
573
- }, async ({ question, quick, repoUrl }) => {
574
- try {
575
- const repoId = await resolveRepoId(repoUrl);
576
- if (!repoId) {
577
- return {
578
- content: [{ type: 'text', text: 'Could not find repository. Make sure the repo is analyzed on merlin.build.' }],
579
- };
580
- }
581
- const response = await client.ask(repoId, question, { quick });
582
- let result = `# Answer\n\n${response.answer}\n`;
583
- if (response.sources && response.sources.length > 0 && !quick) {
584
- result += `\n## Sources\n`;
585
- const highRelevance = response.sources.filter(s => s.relevance === 'high');
586
- const mediumRelevance = response.sources.filter(s => s.relevance === 'medium');
587
- if (highRelevance.length > 0) {
588
- result += `\n**Key Sources:**\n`;
589
- highRelevance.forEach(s => {
590
- result += `- ${s.path || s.name} (${s.type})\n`;
591
- });
592
- }
593
- if (mediumRelevance.length > 0) {
594
- result += `\n**Related:**\n`;
595
- mediumRelevance.slice(0, 5).forEach(s => {
596
- result += `- ${s.path || s.name}\n`;
597
- });
598
- }
599
- }
600
- if (response.confidence) {
601
- const confidenceEmoji = response.confidence === 'high' ? '✓' : response.confidence === 'medium' ? '~' : '?';
602
- result += `\n---\n_Confidence: ${confidenceEmoji} ${response.confidence} | Method: ${response.searchMethod || 'unknown'}_`;
603
- }
604
- return { content: [{ type: 'text', text: result }] };
605
- }
606
- catch (error) {
607
- return {
608
- content: [{ type: 'text', text: `Error answering question: ${error instanceof Error ? error.message : 'Unknown error'}` }],
609
- isError: true,
610
- };
611
- }
612
- });
613
- }
614
- //# sourceMappingURL=context.js.map