@agentbrain/mcp-server 1.4.67 → 1.4.74
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/index.js +28 -20
- package/dist/index.js.map +1 -1
- package/dist/tools/generate-context.d.ts +24 -0
- package/dist/tools/generate-context.d.ts.map +1 -0
- package/dist/tools/generate-context.js +73 -0
- package/dist/tools/generate-context.js.map +1 -0
- package/dist/tools/load-context.d.ts +13 -0
- package/dist/tools/load-context.d.ts.map +1 -1
- package/dist/tools/load-context.js +484 -102
- package/dist/tools/load-context.js.map +1 -1
- package/dist/tools/save-context.d.ts +19 -13
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +89 -184
- package/dist/tools/save-context.js.map +1 -1
- package/dist/tools/save-error.d.ts +47 -0
- package/dist/tools/save-error.d.ts.map +1 -0
- package/dist/tools/save-error.js +109 -0
- package/dist/tools/save-error.js.map +1 -0
- package/dist/tools/setup-repo.d.ts +10 -0
- package/dist/tools/setup-repo.d.ts.map +1 -1
- package/dist/tools/setup-repo.js +153 -8
- package/dist/tools/setup-repo.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// MCP tool: load_context - load context docs at session start
|
|
2
|
-
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
|
3
3
|
import { existsSync } from 'node:fs';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
|
-
import { loadAIConfig, generateContext,
|
|
6
|
+
import { loadAIConfig, generateContext, getGitHash, loadCache, getPendingDoomForMCP, ensureGitignore, installPostCommitHook, isGitRepository, createAgentFilesShared, generateContextFiles, } from '@agentbrain/core';
|
|
7
7
|
/**
|
|
8
8
|
* Expand path: handles ~, relative paths, etc.
|
|
9
9
|
*/
|
|
@@ -13,118 +13,483 @@ function expandPath(path) {
|
|
|
13
13
|
}
|
|
14
14
|
return resolve(path);
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Order context files: specific order for known files, alphabetical for others, errors.md last
|
|
18
|
+
*/
|
|
19
|
+
function orderContextFiles(files) {
|
|
20
|
+
const order = [
|
|
21
|
+
'context.md',
|
|
22
|
+
'branch-context.md',
|
|
23
|
+
'agent-hints.md',
|
|
24
|
+
'run-commands.md',
|
|
25
|
+
'schema.md',
|
|
26
|
+
'dependencies.md',
|
|
27
|
+
'dependency-map.md',
|
|
28
|
+
'patterns.md',
|
|
29
|
+
'decisions.md',
|
|
30
|
+
'inventory.md',
|
|
31
|
+
'testing.md',
|
|
32
|
+
'environment.md',
|
|
33
|
+
];
|
|
34
|
+
const ordered = [];
|
|
35
|
+
const remaining = [];
|
|
36
|
+
let errorsFile = null;
|
|
37
|
+
for (const file of files) {
|
|
38
|
+
if (file === 'errors.md') {
|
|
39
|
+
errorsFile = file;
|
|
40
|
+
}
|
|
41
|
+
else if (order.includes(file)) {
|
|
42
|
+
ordered.push(file);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
remaining.push(file);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Sort known files by order array
|
|
49
|
+
ordered.sort((a, b) => order.indexOf(a) - order.indexOf(b));
|
|
50
|
+
// Sort remaining alphabetically
|
|
51
|
+
remaining.sort();
|
|
52
|
+
// Combine: ordered + remaining + errors.md
|
|
53
|
+
const result = [...ordered, ...remaining];
|
|
54
|
+
if (errorsFile) {
|
|
55
|
+
result.push(errorsFile);
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get description for a context file
|
|
61
|
+
*/
|
|
62
|
+
function getFileDescription(filename) {
|
|
63
|
+
const descriptions = {
|
|
64
|
+
'context.md': 'architecture overview',
|
|
65
|
+
'schema.md': 'database models',
|
|
66
|
+
'patterns.md': 'coding conventions',
|
|
67
|
+
'dependencies.md': 'package versions',
|
|
68
|
+
'dependency-map.md': 'module relationships',
|
|
69
|
+
'testing.md': 'test patterns',
|
|
70
|
+
'decisions.md': 'tech stack decisions',
|
|
71
|
+
'errors.md': 'known issues and solutions',
|
|
72
|
+
'branch-context.md': 'current branch and recent commits',
|
|
73
|
+
'inventory.md': 'components and utilities',
|
|
74
|
+
'environment.md': 'environment variables',
|
|
75
|
+
'run-commands.md': 'available commands',
|
|
76
|
+
'agent-hints.md': 'entry points and stack',
|
|
77
|
+
};
|
|
78
|
+
return descriptions[filename] || 'additional context';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build a slim summary from context.md, run-commands.md, and agent-hints.md
|
|
82
|
+
* Extracts key sections without full content. Keep under 60 lines total.
|
|
83
|
+
*/
|
|
84
|
+
function buildSlimSummary(contextContent, availableFiles, runCommandsContent, agentHintsContent) {
|
|
85
|
+
const lines = contextContent.split('\n');
|
|
86
|
+
let projectName = 'Project';
|
|
87
|
+
let techStack = [];
|
|
88
|
+
let structure = [];
|
|
89
|
+
let modules = [];
|
|
90
|
+
// Extract project name (first # heading)
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
if (line.startsWith('# ') && !projectName) {
|
|
93
|
+
projectName = line.replace('# ', '').trim();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Extract tech stack (look for "Tech Stack", "Stack", or "Technologies" section)
|
|
98
|
+
let inTechStack = false;
|
|
99
|
+
for (let i = 0; i < lines.length; i++) {
|
|
100
|
+
const line = lines[i];
|
|
101
|
+
if (/^##\s+(Tech Stack|Stack|Technologies|Technology)/i.test(line)) {
|
|
102
|
+
inTechStack = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (inTechStack) {
|
|
106
|
+
if (line.startsWith('##'))
|
|
107
|
+
break; // Next section
|
|
108
|
+
if (line.trim().startsWith('-') || line.trim().startsWith('*')) {
|
|
109
|
+
const item = line.replace(/^[\s\-\*]+/, '').split(':')[0].trim();
|
|
110
|
+
if (item && techStack.length < 6) {
|
|
111
|
+
// Reduced from 8 to keep summary compact
|
|
112
|
+
techStack.push(item);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Extract entry points from agent-hints.md if available
|
|
118
|
+
const entryPoints = [];
|
|
119
|
+
if (agentHintsContent) {
|
|
120
|
+
const hintLines = agentHintsContent.split('\n');
|
|
121
|
+
let inEntryPoints = false;
|
|
122
|
+
for (const line of hintLines) {
|
|
123
|
+
if (line.includes('## Entry Points')) {
|
|
124
|
+
inEntryPoints = true;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (inEntryPoints) {
|
|
128
|
+
if (line.startsWith('##'))
|
|
129
|
+
break;
|
|
130
|
+
if (line.trim().startsWith('-')) {
|
|
131
|
+
const entry = line.replace(/^[\s\-\*]+/, '').trim();
|
|
132
|
+
if (entry && !entry.startsWith('*') && entryPoints.length < 3) {
|
|
133
|
+
entryPoints.push(entry);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Extract run commands from run-commands.md if available
|
|
140
|
+
const runCommands = [];
|
|
141
|
+
if (runCommandsContent) {
|
|
142
|
+
const cmdLines = runCommandsContent.split('\n');
|
|
143
|
+
let currentCategory = '';
|
|
144
|
+
for (const line of cmdLines) {
|
|
145
|
+
if (line.startsWith('### ')) {
|
|
146
|
+
currentCategory = line.replace('### ', '').trim();
|
|
147
|
+
runCommands.push({ category: currentCategory, commands: [] });
|
|
148
|
+
}
|
|
149
|
+
else if (line.trim().startsWith('-') && currentCategory) {
|
|
150
|
+
const cmd = line.replace(/^[\s\-\*]+/, '').trim();
|
|
151
|
+
if (cmd) {
|
|
152
|
+
const lastCategory = runCommands[runCommands.length - 1];
|
|
153
|
+
if (lastCategory && lastCategory.commands.length < 3) {
|
|
154
|
+
// Max 3 per category to keep compact
|
|
155
|
+
lastCategory.commands.push(cmd);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Build slim summary (keep under 60 lines)
|
|
162
|
+
let summary = `# ${projectName} - Context Summary\n\n`;
|
|
163
|
+
// Entry points (compact, high-value)
|
|
164
|
+
if (entryPoints.length > 0) {
|
|
165
|
+
summary += `## Entry Points\n${entryPoints.join(', ')}\n\n`;
|
|
166
|
+
}
|
|
167
|
+
// Tech stack
|
|
168
|
+
if (techStack.length > 0) {
|
|
169
|
+
summary += `## Tech Stack\n${techStack.join(', ')}\n\n`;
|
|
170
|
+
}
|
|
171
|
+
// Run commands (compact format)
|
|
172
|
+
if (runCommands.length > 0) {
|
|
173
|
+
summary += `## Run Commands\n`;
|
|
174
|
+
for (const { category, commands } of runCommands.slice(0, 3)) {
|
|
175
|
+
// Max 3 categories
|
|
176
|
+
if (commands.length > 0) {
|
|
177
|
+
summary += `**${category}**: ${commands.slice(0, 2).join(', ')}\n`; // Max 2 per category
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
summary += '\n';
|
|
181
|
+
}
|
|
182
|
+
// Available files (compact)
|
|
183
|
+
summary += `## Available Context Files\n`;
|
|
184
|
+
const importantFiles = availableFiles.filter((f) => ['context.md', 'patterns.md', 'decisions.md', 'schema.md'].includes(f));
|
|
185
|
+
const otherFiles = availableFiles.filter((f) => !importantFiles.includes(f));
|
|
186
|
+
for (const file of importantFiles) {
|
|
187
|
+
summary += `- .agentbrain/${file} — ${getFileDescription(file)}\n`;
|
|
188
|
+
}
|
|
189
|
+
if (otherFiles.length > 0) {
|
|
190
|
+
summary += `... and ${otherFiles.length} more files\n`;
|
|
191
|
+
}
|
|
192
|
+
summary += `\n## How to Load More\n`;
|
|
193
|
+
summary += `- Full context: load_context({ full: true })\n`;
|
|
194
|
+
summary += `- Specific file: load_context({ files: ['schema.md'] })\n`;
|
|
195
|
+
summary += `- Generate new context: generate_context({ focus: "area name" })\n`;
|
|
196
|
+
return summary;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Bootstrap a repository with auto-generated context files
|
|
200
|
+
* Uses AST parser to generate factual files in ~1 second
|
|
201
|
+
* Agent only needs to write 3 interpretive files
|
|
202
|
+
*/
|
|
203
|
+
async function bootstrapRepo(repoPath) {
|
|
204
|
+
const contextDir = join(repoPath, '.agentbrain');
|
|
205
|
+
// 1. Create .agentbrain/ directory
|
|
206
|
+
if (!existsSync(contextDir)) {
|
|
207
|
+
await mkdir(contextDir, { recursive: true });
|
|
208
|
+
}
|
|
209
|
+
// 2. Auto-generate factual context files using parser (fast, ~1 second)
|
|
210
|
+
const generatedFiles = await generateContextFiles(repoPath);
|
|
211
|
+
// 3. Save generated files to .agentbrain/
|
|
212
|
+
for (const file of generatedFiles) {
|
|
213
|
+
await writeFile(join(contextDir, file.name), file.content, 'utf-8');
|
|
214
|
+
}
|
|
215
|
+
// 4. Get current git hash and write cache.json
|
|
216
|
+
const gitHash = await getGitHash(repoPath);
|
|
217
|
+
const docs = {};
|
|
218
|
+
for (const file of generatedFiles) {
|
|
219
|
+
const baseName = file.name.replace('.md', '');
|
|
220
|
+
docs[baseName] = {
|
|
221
|
+
type: baseName,
|
|
222
|
+
content: file.content,
|
|
223
|
+
savedAt: new Date().toISOString(),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
const cachePath = join(contextDir, 'cache.json');
|
|
227
|
+
const cache = {
|
|
228
|
+
gitHash,
|
|
229
|
+
docs,
|
|
230
|
+
savedAt: new Date().toISOString(),
|
|
231
|
+
};
|
|
232
|
+
await writeFile(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
233
|
+
// Write setup-source marker
|
|
234
|
+
const setupSourcePath = join(contextDir, 'setup-source.json');
|
|
235
|
+
await writeFile(setupSourcePath, JSON.stringify({ source: 'mcp-bootstrap', setupAt: new Date().toISOString() }, null, 2), 'utf-8');
|
|
236
|
+
// 5. Update .gitignore
|
|
237
|
+
try {
|
|
238
|
+
await ensureGitignore(repoPath);
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
console.error('[bootstrap] .gitignore update failed:', err);
|
|
242
|
+
}
|
|
243
|
+
// 6. Install git hook if this is a git repository
|
|
244
|
+
if (isGitRepository(repoPath)) {
|
|
245
|
+
try {
|
|
246
|
+
await installPostCommitHook(repoPath);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.error('[bootstrap] Git hook installation failed:', err);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// 7. Update AGENTS.md with file list
|
|
253
|
+
const fileNames = generatedFiles.map((f) => f.name);
|
|
254
|
+
try {
|
|
255
|
+
await createAgentFilesShared(repoPath, fileNames);
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
console.error('[bootstrap] AGENTS.md update failed:', err);
|
|
259
|
+
}
|
|
260
|
+
// 8. Return instruction for agent to write the 3 interpretive files only
|
|
261
|
+
const fileList = generatedFiles.map((f) => `✓ .agentbrain/${f.name}`).join('\n');
|
|
262
|
+
return `AgentBrain has auto-generated ${generatedFiles.length} context files from your codebase in ~2 seconds:
|
|
263
|
+
|
|
264
|
+
${fileList}
|
|
265
|
+
|
|
266
|
+
These cover ~80% of what you need to work effectively in this codebase.
|
|
267
|
+
|
|
268
|
+
Now generate 3 short interpretive files. Call save_context() when done.
|
|
269
|
+
|
|
270
|
+
IMPORTANT: Keep each file SHORT and focused on NON-OBVIOUS information only. The factual files already exist. Don't repeat what's already in schema.md, dependencies.md, etc.
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
1. context.md (target: 20-30 lines)
|
|
275
|
+
Only include:
|
|
276
|
+
- What this product does (2 sentences max)
|
|
277
|
+
- Key modules list (name + 1 line purpose each)
|
|
278
|
+
- 1-2 non-obvious data flows
|
|
279
|
+
Skip: tech stack (in dependencies.md), file structure (in inventory.md)
|
|
280
|
+
|
|
281
|
+
2. patterns.md (target: 20-40 lines)
|
|
282
|
+
Only include:
|
|
283
|
+
- Key utilities to ALWAYS use (not reinvent)
|
|
284
|
+
- 3-5 anti-patterns specific to this codebase
|
|
285
|
+
- Error handling approach
|
|
286
|
+
- 1-2 non-obvious conventions
|
|
287
|
+
Skip: standard framework patterns, obvious conventions
|
|
288
|
+
|
|
289
|
+
3. decisions.md (target: 10-20 lines)
|
|
290
|
+
Only include:
|
|
291
|
+
- What must NOT be changed (compliance, legacy)
|
|
292
|
+
- Why key non-obvious libraries were chosen
|
|
293
|
+
- 1-2 architectural choices that surprise devs
|
|
294
|
+
Skip: obvious choices, anything self-evident from the code
|
|
295
|
+
|
|
296
|
+
Total target: under 80 lines across all 3 files.
|
|
297
|
+
Short, opinionated, non-obvious = high signal.
|
|
298
|
+
Long, comprehensive, obvious = noise.`;
|
|
299
|
+
}
|
|
16
300
|
export async function loadContext(input) {
|
|
17
|
-
const { repo_path, force_refresh = false } = input;
|
|
301
|
+
const { repo_path, force_refresh = false, full = false, files: requestedFiles } = input;
|
|
18
302
|
// Expand path to handle ~, relative paths, etc.
|
|
19
303
|
const expandedPath = expandPath(repo_path);
|
|
20
304
|
const contextDir = join(expandedPath, '.agentbrain');
|
|
305
|
+
// Bootstrap if .agentbrain/ doesn't exist
|
|
306
|
+
if (!existsSync(contextDir)) {
|
|
307
|
+
const bootstrapMessage = await bootstrapRepo(expandedPath);
|
|
308
|
+
return {
|
|
309
|
+
content: bootstrapMessage,
|
|
310
|
+
fromCache: false,
|
|
311
|
+
tokensUsed: 0,
|
|
312
|
+
doom_warning: null,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
21
315
|
// Get current git hash for staleness check
|
|
22
316
|
const currentGitHash = await getGitHash(expandedPath);
|
|
23
|
-
// Try to load from disk first
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
317
|
+
// Try to load from disk first - read ALL .md files
|
|
318
|
+
if (!force_refresh && existsSync(contextDir)) {
|
|
319
|
+
try {
|
|
320
|
+
const allFiles = await readdir(contextDir);
|
|
321
|
+
const mdFiles = allFiles.filter((f) => f.endsWith('.md'));
|
|
322
|
+
if (mdFiles.length > 0) {
|
|
323
|
+
// Order files properly
|
|
324
|
+
const orderedFiles = orderContextFiles(mdFiles);
|
|
325
|
+
// Determine which files to load based on mode
|
|
326
|
+
let filesToLoad = orderedFiles;
|
|
327
|
+
if (requestedFiles && requestedFiles.length > 0) {
|
|
328
|
+
// Load only requested files
|
|
329
|
+
filesToLoad = orderedFiles.filter((f) => requestedFiles.includes(f));
|
|
330
|
+
}
|
|
331
|
+
else if (!full) {
|
|
332
|
+
// Slim mode: load context.md, run-commands.md, and agent-hints.md for summary
|
|
333
|
+
filesToLoad = orderedFiles.filter((f) => ['context.md', 'run-commands.md', 'agent-hints.md'].includes(f));
|
|
334
|
+
}
|
|
335
|
+
// Load files
|
|
336
|
+
const fileContents = [];
|
|
337
|
+
for (const file of filesToLoad) {
|
|
338
|
+
const filePath = join(contextDir, file);
|
|
339
|
+
const content = await readFile(filePath, 'utf-8');
|
|
340
|
+
const title = file.replace('.md', '').replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
341
|
+
fileContents.push({ name: file, content: `# ${title}\n\n${content}` });
|
|
342
|
+
}
|
|
343
|
+
// Build output based on mode
|
|
344
|
+
let combined;
|
|
345
|
+
if (!full && !requestedFiles) {
|
|
346
|
+
// Slim mode: build summary from context.md, run-commands.md, and agent-hints.md
|
|
347
|
+
const contextContent = fileContents.find((f) => f.name === 'context.md');
|
|
348
|
+
const runCommandsContent = fileContents.find((f) => f.name === 'run-commands.md');
|
|
349
|
+
const agentHintsContent = fileContents.find((f) => f.name === 'agent-hints.md');
|
|
350
|
+
if (contextContent) {
|
|
351
|
+
combined = buildSlimSummary(contextContent.content, orderedFiles, runCommandsContent?.content, agentHintsContent?.content);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
// Fallback if context.md doesn't exist
|
|
355
|
+
combined = buildSlimSummary('# Project\n\n', orderedFiles, runCommandsContent?.content, agentHintsContent?.content);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
// Full mode or specific files: concatenate all loaded files
|
|
360
|
+
combined = fileContents.map((f) => f.content).join('\n\n---\n\n');
|
|
361
|
+
}
|
|
362
|
+
// Post-processing: If cache.json or setup-source.json is missing, agent wrote files directly
|
|
363
|
+
const cachePath = join(contextDir, 'cache.json');
|
|
364
|
+
const setupSourcePath = join(contextDir, 'setup-source.json');
|
|
365
|
+
const cacheExists = existsSync(cachePath);
|
|
366
|
+
const setupSourceExists = existsSync(setupSourcePath);
|
|
367
|
+
if (!cacheExists || !setupSourceExists) {
|
|
368
|
+
// Agent wrote files directly — run post-processing
|
|
369
|
+
try {
|
|
370
|
+
// Write cache.json with ALL files
|
|
371
|
+
if (!cacheExists) {
|
|
372
|
+
const docs = {};
|
|
373
|
+
for (const file of orderedFiles) {
|
|
374
|
+
const filePath = join(contextDir, file);
|
|
375
|
+
const content = await readFile(filePath, 'utf-8');
|
|
376
|
+
const baseName = file.replace('.md', '');
|
|
377
|
+
docs[baseName] = {
|
|
378
|
+
type: baseName,
|
|
379
|
+
content,
|
|
380
|
+
savedAt: new Date().toISOString(),
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const cache = {
|
|
384
|
+
gitHash: currentGitHash,
|
|
385
|
+
docs,
|
|
60
386
|
savedAt: new Date().toISOString(),
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
387
|
+
};
|
|
388
|
+
await writeFile(cachePath, JSON.stringify(cache, null, 2), 'utf-8');
|
|
389
|
+
}
|
|
390
|
+
// Write setup-source.json
|
|
391
|
+
if (!setupSourceExists) {
|
|
392
|
+
await writeFile(setupSourcePath, JSON.stringify({ source: 'mcp', setupAt: new Date().toISOString() }, null, 2), 'utf-8');
|
|
393
|
+
}
|
|
394
|
+
// Update .gitignore
|
|
395
|
+
await ensureGitignore(expandedPath);
|
|
396
|
+
// Install git hook if this is a git repository
|
|
397
|
+
if (isGitRepository(expandedPath)) {
|
|
398
|
+
await installPostCommitHook(expandedPath);
|
|
399
|
+
}
|
|
400
|
+
// Create agent files with ALL context files
|
|
401
|
+
await createAgentFilesShared(expandedPath, orderedFiles);
|
|
402
|
+
// Silent success - only log errors
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
console.error('[load_context] Post-processing failed:', err);
|
|
406
|
+
}
|
|
66
407
|
}
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
408
|
+
// Check if cached git hash matches current HEAD
|
|
409
|
+
const cache = await loadCache(expandedPath);
|
|
410
|
+
const isStale = cache && cache.gitHash !== currentGitHash;
|
|
411
|
+
// Check for doom loop warnings
|
|
412
|
+
const doomWarning = await getPendingDoomForMCP(expandedPath);
|
|
413
|
+
const footer = '\n\n---\n_AgentBrain context loaded. Check doom_warning field for any detected issues._';
|
|
414
|
+
return {
|
|
415
|
+
content: combined + footer,
|
|
416
|
+
fromCache: true,
|
|
417
|
+
tokensUsed: 0,
|
|
418
|
+
...(isStale && {
|
|
419
|
+
stale: true,
|
|
420
|
+
cached_sha: cache.gitHash,
|
|
421
|
+
current_sha: currentGitHash,
|
|
422
|
+
message: 'Context may be outdated. Run agentbrain init to refresh.',
|
|
423
|
+
}),
|
|
424
|
+
doom_warning: doomWarning,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
// If there's an error reading files, fall through to cache check
|
|
430
|
+
console.error('[load_context] Error reading .agentbrain files:', err);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Check cache validity - load from cache.json if available
|
|
434
|
+
const cache = await loadCache(expandedPath);
|
|
435
|
+
if (!force_refresh && cache && cache.docs) {
|
|
436
|
+
// Extract all doc names and order them
|
|
437
|
+
const docNames = Object.keys(cache.docs).map((name) => `${name}.md`);
|
|
438
|
+
const orderedDocs = orderContextFiles(docNames);
|
|
439
|
+
// Determine which docs to load based on mode
|
|
440
|
+
let docsToLoad = orderedDocs;
|
|
441
|
+
if (requestedFiles && requestedFiles.length > 0) {
|
|
442
|
+
// Load only requested files
|
|
443
|
+
docsToLoad = orderedDocs.filter((f) => requestedFiles.includes(f));
|
|
444
|
+
}
|
|
445
|
+
else if (!full) {
|
|
446
|
+
// Slim mode: only load context for summary
|
|
447
|
+
docsToLoad = orderedDocs.filter((f) => f === 'context.md');
|
|
448
|
+
}
|
|
449
|
+
// Build content from cache
|
|
450
|
+
const fileContents = [];
|
|
451
|
+
for (const docFile of docsToLoad) {
|
|
452
|
+
const docName = docFile.replace('.md', '');
|
|
453
|
+
const doc = cache.docs[docName];
|
|
454
|
+
if (doc) {
|
|
455
|
+
const title = docName.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
|
|
456
|
+
fileContents.push(`# ${title}\n\n${doc.content}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
if (fileContents.length > 0) {
|
|
460
|
+
// Build output based on mode
|
|
461
|
+
let combined;
|
|
462
|
+
if (!full && !requestedFiles) {
|
|
463
|
+
// Slim mode: build summary
|
|
464
|
+
const contextContent = fileContents.find((c) => c.startsWith('# Context'));
|
|
465
|
+
if (contextContent) {
|
|
466
|
+
combined = buildSlimSummary(contextContent, orderedDocs);
|
|
70
467
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
// Install git hook if this is a git repository
|
|
74
|
-
if (isGitRepository(expandedPath)) {
|
|
75
|
-
await installPostCommitHook(expandedPath);
|
|
468
|
+
else {
|
|
469
|
+
combined = buildSlimSummary('# Project\n\n', orderedDocs);
|
|
76
470
|
}
|
|
77
|
-
// Create agent files
|
|
78
|
-
await createAgentFilesShared(expandedPath);
|
|
79
|
-
console.error('[load_context] Post-processing completed for manually-written files');
|
|
80
471
|
}
|
|
81
|
-
|
|
82
|
-
|
|
472
|
+
else {
|
|
473
|
+
// Full mode or specific files
|
|
474
|
+
combined = fileContents.join('\n\n---\n\n');
|
|
83
475
|
}
|
|
476
|
+
const isStale = cache.gitHash !== currentGitHash;
|
|
477
|
+
// Check for doom loop warnings
|
|
478
|
+
const doomWarning = await getPendingDoomForMCP(expandedPath);
|
|
479
|
+
const footer = '\n\n---\n_AgentBrain context loaded. Check doom_warning field for any detected issues._';
|
|
480
|
+
return {
|
|
481
|
+
content: combined + footer,
|
|
482
|
+
fromCache: true,
|
|
483
|
+
tokensUsed: 0,
|
|
484
|
+
...(isStale && {
|
|
485
|
+
stale: true,
|
|
486
|
+
cached_sha: cache.gitHash,
|
|
487
|
+
current_sha: currentGitHash,
|
|
488
|
+
message: 'Context may be outdated. Run agentbrain init to refresh.',
|
|
489
|
+
}),
|
|
490
|
+
doom_warning: doomWarning,
|
|
491
|
+
};
|
|
84
492
|
}
|
|
85
|
-
// Check if cached git hash matches current HEAD
|
|
86
|
-
const cache = await loadCache(expandedPath);
|
|
87
|
-
const isStale = cache && cache.gitHash !== currentGitHash;
|
|
88
|
-
// Check for doom loop warnings
|
|
89
|
-
const doomWarning = await getPendingDoomForMCP(expandedPath);
|
|
90
|
-
const footer = '\n\n---\n_AgentBrain context loaded. Check doom_warning field for any detected issues._';
|
|
91
|
-
return {
|
|
92
|
-
content: combined + footer,
|
|
93
|
-
fromCache: true,
|
|
94
|
-
tokensUsed: 0,
|
|
95
|
-
...(isStale && {
|
|
96
|
-
stale: true,
|
|
97
|
-
cached_sha: cache.gitHash,
|
|
98
|
-
current_sha: currentGitHash,
|
|
99
|
-
message: 'Context may be outdated. Run agentbrain init to refresh.',
|
|
100
|
-
}),
|
|
101
|
-
doom_warning: doomWarning,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
// Check cache validity (using currentGitHash from above)
|
|
105
|
-
const cachedContext = await getCachedDoc(expandedPath, currentGitHash, 'context');
|
|
106
|
-
const cachedDepMap = await getCachedDoc(expandedPath, currentGitHash, 'dependency-map');
|
|
107
|
-
const cachedPatterns = await getCachedDoc(expandedPath, currentGitHash, 'patterns');
|
|
108
|
-
if (!force_refresh && cachedContext && cachedDepMap && cachedPatterns) {
|
|
109
|
-
const combined = `# Repository Context\n\n${cachedContext.content}\n\n---\n\n# Dependency Map\n\n${cachedDepMap.content}\n\n---\n\n# Patterns\n\n${cachedPatterns.content}`;
|
|
110
|
-
// Check if cached git hash matches current HEAD
|
|
111
|
-
const cache = await loadCache(expandedPath);
|
|
112
|
-
const isStale = cache && cache.gitHash !== currentGitHash;
|
|
113
|
-
// Check for doom loop warnings
|
|
114
|
-
const doomWarning = await getPendingDoomForMCP(expandedPath);
|
|
115
|
-
const footer = '\n\n---\n_AgentBrain context loaded. Check doom_warning field for any detected issues._';
|
|
116
|
-
return {
|
|
117
|
-
content: combined + footer,
|
|
118
|
-
fromCache: true,
|
|
119
|
-
tokensUsed: 0,
|
|
120
|
-
...(isStale && {
|
|
121
|
-
stale: true,
|
|
122
|
-
cached_sha: cache.gitHash,
|
|
123
|
-
current_sha: currentGitHash,
|
|
124
|
-
message: 'Context may be outdated. Run agentbrain init to refresh.',
|
|
125
|
-
}),
|
|
126
|
-
doom_warning: doomWarning,
|
|
127
|
-
};
|
|
128
493
|
}
|
|
129
494
|
// Need to generate - no API key path returns bootstrap message
|
|
130
495
|
let aiConfig = null;
|
|
@@ -167,7 +532,13 @@ Call: setup_repo({ repo_path: '${expandedPath}' })`,
|
|
|
167
532
|
}
|
|
168
533
|
export const loadContextSchema = {
|
|
169
534
|
name: 'load_context',
|
|
170
|
-
description: 'Load codebase context
|
|
535
|
+
description: 'Load codebase context.\n\n' +
|
|
536
|
+
'Default: Returns slim summary showing available context files. Fast, low token usage (~100 lines).\n\n' +
|
|
537
|
+
'Options:\n' +
|
|
538
|
+
' full: true → load all context files (large, 1000+ lines)\n' +
|
|
539
|
+
' files: ["schema.md"] → load specific files only\n' +
|
|
540
|
+
' force_refresh: true → regenerate context\n\n' +
|
|
541
|
+
'If first time setup, automatically scans repo and returns setup instructions.',
|
|
171
542
|
inputSchema: {
|
|
172
543
|
type: 'object',
|
|
173
544
|
properties: {
|
|
@@ -179,6 +550,17 @@ export const loadContextSchema = {
|
|
|
179
550
|
type: 'boolean',
|
|
180
551
|
description: 'Force regeneration even if cache is valid (default: false)',
|
|
181
552
|
},
|
|
553
|
+
full: {
|
|
554
|
+
type: 'boolean',
|
|
555
|
+
description: 'Load all context files instead of slim summary (default: false)',
|
|
556
|
+
},
|
|
557
|
+
files: {
|
|
558
|
+
type: 'array',
|
|
559
|
+
description: 'Load specific files only (e.g., ["schema.md", "patterns.md"])',
|
|
560
|
+
items: {
|
|
561
|
+
type: 'string',
|
|
562
|
+
},
|
|
563
|
+
},
|
|
182
564
|
},
|
|
183
565
|
required: ['repo_path'],
|
|
184
566
|
},
|