@getvibeguard/cli 1.0.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/README.md +166 -0
- package/dashboard/dist/assets/Tableau10-B-NsZVaP.js +1 -0
- package/dashboard/dist/assets/arc-CZ1oDkm-.js +1 -0
- package/dashboard/dist/assets/array-BKyUJesY.js +1 -0
- package/dashboard/dist/assets/blockDiagram-c4efeb88-BLITAZDl.js +118 -0
- package/dashboard/dist/assets/c4Diagram-c83219d4-DErNj891.js +10 -0
- package/dashboard/dist/assets/channel-sWBGOq9p.js +1 -0
- package/dashboard/dist/assets/classDiagram-beda092f-Bj8IB6RW.js +2 -0
- package/dashboard/dist/assets/classDiagram-v2-2358418a-BZtd2g5W.js +2 -0
- package/dashboard/dist/assets/clone-DtEyH3ya.js +1 -0
- package/dashboard/dist/assets/createText-1719965b-DkNrTk-5.js +7 -0
- package/dashboard/dist/assets/edges-96097737-CYxl0j3g.js +4 -0
- package/dashboard/dist/assets/erDiagram-0228fc6a-ClEp9Atg.js +51 -0
- package/dashboard/dist/assets/flowDb-c6c81e3f-ClDJCJwu.js +10 -0
- package/dashboard/dist/assets/flowDiagram-50d868cf-DYxwClWS.js +4 -0
- package/dashboard/dist/assets/flowDiagram-v2-4f6560a1-D7vEnO1T.js +1 -0
- package/dashboard/dist/assets/flowchart-elk-definition-6af322e1-C-wX1mNx.js +139 -0
- package/dashboard/dist/assets/ganttDiagram-a2739b55-DTeLavAj.js +257 -0
- package/dashboard/dist/assets/gitGraphDiagram-82fe8481-ClxNoYya.js +70 -0
- package/dashboard/dist/assets/graph-CT-F3Got.js +1 -0
- package/dashboard/dist/assets/index-5325376f-C-jTCYZA.js +1 -0
- package/dashboard/dist/assets/index-CvYvquQR.js +283 -0
- package/dashboard/dist/assets/index-n43poL1x.css +1 -0
- package/dashboard/dist/assets/infoDiagram-8eee0895-Zljudo5L.js +7 -0
- package/dashboard/dist/assets/init-Gi6I4Gst.js +1 -0
- package/dashboard/dist/assets/journeyDiagram-c64418c1-DXzTbuum.js +139 -0
- package/dashboard/dist/assets/katex-Cu_Erd72.js +261 -0
- package/dashboard/dist/assets/layout-CVO3EizT.js +1 -0
- package/dashboard/dist/assets/line-CIgln-0z.js +1 -0
- package/dashboard/dist/assets/linear-bmIUMQqg.js +1 -0
- package/dashboard/dist/assets/mindmap-definition-8da855dc-BDLTNZYk.js +425 -0
- package/dashboard/dist/assets/ordinal-Cboi1Yqb.js +1 -0
- package/dashboard/dist/assets/path-CbwjOpE9.js +1 -0
- package/dashboard/dist/assets/pieDiagram-a8764435-DtcOKNPc.js +35 -0
- package/dashboard/dist/assets/quadrantDiagram-1e28029f-zoSI_Ltf.js +7 -0
- package/dashboard/dist/assets/requirementDiagram-08caed73-TsQZ9lTB.js +52 -0
- package/dashboard/dist/assets/sankeyDiagram-a04cb91d-DE5ciDwD.js +8 -0
- package/dashboard/dist/assets/sequenceDiagram-c5b8d532-DhabPb2n.js +122 -0
- package/dashboard/dist/assets/stateDiagram-1ecb1508-Bg2q_YNx.js +1 -0
- package/dashboard/dist/assets/stateDiagram-v2-c2b004d7-Bs5iRjYB.js +1 -0
- package/dashboard/dist/assets/styles-b4e223ce-DchmAmav.js +160 -0
- package/dashboard/dist/assets/styles-ca3715f6-Bu5zjaDx.js +207 -0
- package/dashboard/dist/assets/styles-d45a18b0-jCaD8baR.js +116 -0
- package/dashboard/dist/assets/svgDrawCommon-b86b1483-BrYVGY4c.js +1 -0
- package/dashboard/dist/assets/timeline-definition-faaaa080--sq0bTHe.js +61 -0
- package/dashboard/dist/assets/xychartDiagram-f5964ef8-ByvzN0uj.js +7 -0
- package/dashboard/dist/index.html +22 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +641 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +184 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/init.d.ts +5 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +501 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +602 -0
- package/dist/index.js.map +1 -0
- package/dist/librarian/autofix.d.ts +34 -0
- package/dist/librarian/autofix.d.ts.map +1 -0
- package/dist/librarian/autofix.js +369 -0
- package/dist/librarian/autofix.js.map +1 -0
- package/dist/librarian/heartbeat.d.ts +38 -0
- package/dist/librarian/heartbeat.d.ts.map +1 -0
- package/dist/librarian/heartbeat.js +98 -0
- package/dist/librarian/heartbeat.js.map +1 -0
- package/dist/librarian/initializer.d.ts +41 -0
- package/dist/librarian/initializer.d.ts.map +1 -0
- package/dist/librarian/initializer.js +202 -0
- package/dist/librarian/initializer.js.map +1 -0
- package/dist/librarian/memory-manager.d.ts +37 -0
- package/dist/librarian/memory-manager.d.ts.map +1 -0
- package/dist/librarian/memory-manager.js +205 -0
- package/dist/librarian/memory-manager.js.map +1 -0
- package/dist/librarian/oracle.d.ts +51 -0
- package/dist/librarian/oracle.d.ts.map +1 -0
- package/dist/librarian/oracle.js +235 -0
- package/dist/librarian/oracle.js.map +1 -0
- package/dist/librarian/summarizer.d.ts +19 -0
- package/dist/librarian/summarizer.d.ts.map +1 -0
- package/dist/librarian/summarizer.js +175 -0
- package/dist/librarian/summarizer.js.map +1 -0
- package/dist/librarian/watcher.d.ts +51 -0
- package/dist/librarian/watcher.d.ts.map +1 -0
- package/dist/librarian/watcher.js +189 -0
- package/dist/librarian/watcher.js.map +1 -0
- package/dist/mcp-server.d.ts +10 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +879 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/services/token-tracker.d.ts +58 -0
- package/dist/services/token-tracker.d.ts.map +1 -0
- package/dist/services/token-tracker.js +178 -0
- package/dist/services/token-tracker.js.map +1 -0
- package/dist/utils/chunker.d.ts +5 -0
- package/dist/utils/chunker.d.ts.map +1 -0
- package/dist/utils/chunker.js +121 -0
- package/dist/utils/chunker.js.map +1 -0
- package/dist/utils/config-manager.d.ts +65 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +243 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/config.d.ts +46 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +171 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/diff-cleaner.d.ts +13 -0
- package/dist/utils/diff-cleaner.d.ts.map +1 -0
- package/dist/utils/diff-cleaner.js +80 -0
- package/dist/utils/diff-cleaner.js.map +1 -0
- package/dist/utils/git.d.ts +71 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +285 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/llm.d.ts +30 -0
- package/dist/utils/llm.d.ts.map +1 -0
- package/dist/utils/llm.js +308 -0
- package/dist/utils/llm.js.map +1 -0
- package/dist/utils/pricing.d.ts +47 -0
- package/dist/utils/pricing.d.ts.map +1 -0
- package/dist/utils/pricing.js +171 -0
- package/dist/utils/pricing.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,879 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* VibeGuard MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Exposes PROJECT_MEMORY.md as an MCP tool for Cursor to access project context.
|
|
6
|
+
* This allows Cursor to refresh its knowledge of the project's architecture,
|
|
7
|
+
* tech stack, and recent decisions.
|
|
8
|
+
*/
|
|
9
|
+
// MCP SDK imports - use subpath imports that match the package exports
|
|
10
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
11
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
12
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
13
|
+
import { MemoryManager } from './librarian/memory-manager.js';
|
|
14
|
+
import { generateSummary } from './utils/llm.js';
|
|
15
|
+
import { getApiKey } from './utils/config.js';
|
|
16
|
+
import * as fs from 'fs/promises';
|
|
17
|
+
import * as path from 'path';
|
|
18
|
+
// Create server instance
|
|
19
|
+
const server = new McpServer({
|
|
20
|
+
name: 'vibeguard-mcp',
|
|
21
|
+
version: '1.0.0',
|
|
22
|
+
}, {
|
|
23
|
+
capabilities: {
|
|
24
|
+
tools: {},
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
// Initialize memory manager
|
|
28
|
+
const repoPath = process.cwd();
|
|
29
|
+
const memoryManager = new MemoryManager(repoPath);
|
|
30
|
+
/**
|
|
31
|
+
* Recursively find the latest modified time of any file in a directory
|
|
32
|
+
*/
|
|
33
|
+
async function getLatestMTime(dir) {
|
|
34
|
+
let latestMTime = null;
|
|
35
|
+
async function walkDir(currentDir, depth = 0) {
|
|
36
|
+
if (depth > 10)
|
|
37
|
+
return; // Limit depth to prevent infinite loops
|
|
38
|
+
try {
|
|
39
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
42
|
+
// Skip common ignore patterns
|
|
43
|
+
if (entry.name.startsWith('.') ||
|
|
44
|
+
entry.name === 'node_modules' ||
|
|
45
|
+
entry.name === 'dist' ||
|
|
46
|
+
entry.name === 'build') {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
await walkDir(fullPath, depth + 1);
|
|
51
|
+
}
|
|
52
|
+
else if (entry.isFile()) {
|
|
53
|
+
try {
|
|
54
|
+
const stats = await fs.stat(fullPath);
|
|
55
|
+
if (!latestMTime || stats.mtime > latestMTime) {
|
|
56
|
+
latestMTime = stats.mtime;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Ignore permission errors or missing files
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
// Ignore permission errors
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(dir);
|
|
71
|
+
await walkDir(dir);
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return latestMTime;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Scan src/ directory and return file structure (for visualization)
|
|
80
|
+
*/
|
|
81
|
+
async function scanSrcDirectory() {
|
|
82
|
+
const srcPath = path.join(repoPath, 'src');
|
|
83
|
+
const files = [];
|
|
84
|
+
async function walkDir(dir, prefix = '', depth = 0) {
|
|
85
|
+
if (depth > 10)
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
89
|
+
const filtered = entries.filter(entry => {
|
|
90
|
+
const name = entry.name;
|
|
91
|
+
return (!name.startsWith('.') &&
|
|
92
|
+
name !== 'node_modules' &&
|
|
93
|
+
name !== 'dist' &&
|
|
94
|
+
name !== 'build');
|
|
95
|
+
});
|
|
96
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
97
|
+
const entry = filtered[i];
|
|
98
|
+
const isLast = i === filtered.length - 1;
|
|
99
|
+
const currentPrefix = isLast ? '└── ' : '├── ';
|
|
100
|
+
const nextPrefix = isLast ? ' ' : '│ ';
|
|
101
|
+
files.push(prefix + currentPrefix + entry.name);
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
const fullPath = path.join(dir, entry.name);
|
|
104
|
+
await walkDir(fullPath, prefix + nextPrefix, depth + 1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
// Ignore permission errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
await fs.access(srcPath);
|
|
114
|
+
await walkDir(srcPath);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw new Error('src/ directory not found. Make sure you are in the project root.');
|
|
118
|
+
}
|
|
119
|
+
return files.join('\n');
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate Mermaid syntax - check for unclosed brackets
|
|
123
|
+
*/
|
|
124
|
+
function validateMermaidSyntax(mermaidCode) {
|
|
125
|
+
const errors = [];
|
|
126
|
+
const mermaidMatch = mermaidCode.match(/```mermaid\n([\s\S]*?)```/);
|
|
127
|
+
if (!mermaidMatch) {
|
|
128
|
+
errors.push('No mermaid code block found');
|
|
129
|
+
return { valid: false, errors };
|
|
130
|
+
}
|
|
131
|
+
const code = mermaidMatch[1];
|
|
132
|
+
const bracketPairs = [
|
|
133
|
+
{ open: '[', close: ']', name: 'square brackets' },
|
|
134
|
+
{ open: '(', close: ')', name: 'parentheses' },
|
|
135
|
+
{ open: '{', close: '}', name: 'curly braces' },
|
|
136
|
+
];
|
|
137
|
+
for (const pair of bracketPairs) {
|
|
138
|
+
let depth = 0;
|
|
139
|
+
for (let i = 0; i < code.length; i++) {
|
|
140
|
+
if (code[i] === pair.open) {
|
|
141
|
+
depth++;
|
|
142
|
+
}
|
|
143
|
+
else if (code[i] === pair.close) {
|
|
144
|
+
depth--;
|
|
145
|
+
if (depth < 0) {
|
|
146
|
+
errors.push(`Unmatched closing ${pair.name} at position ${i}`);
|
|
147
|
+
return { valid: false, errors };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (depth > 0) {
|
|
152
|
+
errors.push(`Unclosed ${pair.name}: ${depth} unclosed bracket(s)`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const subgraphOpen = (code.match(/subgraph\s+[^\s]+\s*\[/g) || []).length;
|
|
156
|
+
const subgraphClose = (code.match(/end\s*$/gm) || []).length;
|
|
157
|
+
if (subgraphOpen !== subgraphClose) {
|
|
158
|
+
errors.push(`Unbalanced subgraphs: ${subgraphOpen} opened, ${subgraphClose} closed`);
|
|
159
|
+
}
|
|
160
|
+
return { valid: errors.length === 0, errors };
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Generate architecture diagram programmatically
|
|
164
|
+
*/
|
|
165
|
+
async function generateDiagram() {
|
|
166
|
+
try {
|
|
167
|
+
// Validate API key is available
|
|
168
|
+
await getApiKey();
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
console.error('Cannot generate diagram: API key not available');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const srcPath = path.join(repoPath, 'src');
|
|
175
|
+
try {
|
|
176
|
+
await fs.access(srcPath);
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
console.error('Cannot generate diagram: src/ directory not found');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Scan src/ directory
|
|
183
|
+
const fileStructure = await scanSrcDirectory();
|
|
184
|
+
// Read PROJECT_MEMORY.md for context
|
|
185
|
+
let projectMemory = '';
|
|
186
|
+
try {
|
|
187
|
+
projectMemory = await memoryManager.readMemory();
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
// Continue without context
|
|
191
|
+
}
|
|
192
|
+
// Build prompt for Gemini
|
|
193
|
+
const today = new Date().toLocaleDateString();
|
|
194
|
+
const prompt = `You are a Senior Software Architect & Mermaid.js Specialist. Your task is to generate a valid Mermaid.js flowchart (TD - Top Down) that visually represents the project's architecture.
|
|
195
|
+
|
|
196
|
+
# PROJECT STRUCTURE
|
|
197
|
+
\`\`\`
|
|
198
|
+
${fileStructure}
|
|
199
|
+
\`\`\`
|
|
200
|
+
|
|
201
|
+
${projectMemory ? `# PROJECT CONTEXT\n\`\`\`\n${projectMemory}\n\`\`\`\n\n` : ''}# REQUIREMENTS
|
|
202
|
+
1. Use Mermaid.js Flowchart syntax (flowchart TD)
|
|
203
|
+
2. Structure:
|
|
204
|
+
- Use subgraphs to group "Internal Logic" vs "External Services"
|
|
205
|
+
- Nodes should represent key components (CLI, MCP Server, Watcher, Gemini API, File System, etc.)
|
|
206
|
+
- Edges should have descriptive text (e.g., "Sends Diffs", "Returns Summary", "Updates Memory")
|
|
207
|
+
3. Color Styling (REQUIRED):
|
|
208
|
+
- Add a classDef section at the end of the Mermaid code (before the closing code block)
|
|
209
|
+
- Define three classes with the following colors:
|
|
210
|
+
* ExternalServices: fill:#e1f5fe (Light Blue)
|
|
211
|
+
* InternalLogic: fill:#e8f5e9 (Light Green)
|
|
212
|
+
* PersistenceFiles: fill:#fff8e1 (Soft Amber)
|
|
213
|
+
- Apply classes to subgraphs or specific nodes:
|
|
214
|
+
* Apply "ExternalServices" class to the "External Services" subgraph
|
|
215
|
+
* Apply "InternalLogic" class to the "Internal Logic" subgraph
|
|
216
|
+
* Apply "PersistenceFiles" class to nodes representing File System, Git Repository, or any persistence layer
|
|
217
|
+
- Example classDef syntax:
|
|
218
|
+
\`\`\`
|
|
219
|
+
classDef ExternalServices fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
|
220
|
+
classDef InternalLogic fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
|
|
221
|
+
classDef PersistenceFiles fill:#fff8e1,stroke:#f57f17,stroke-width:2px
|
|
222
|
+
\`\`\`
|
|
223
|
+
- Apply classes using: \`class subgraphId ExternalServices\` or \`class nodeId PersistenceFiles\`
|
|
224
|
+
4. Syntax Rules:
|
|
225
|
+
- All node IDs must be alphanumeric (use [ ] for labels with spaces)
|
|
226
|
+
- Avoid special characters like & or > inside labels unless escaped
|
|
227
|
+
- Ensure all brackets are properly closed
|
|
228
|
+
- All subgraphs must have matching "end" statements
|
|
229
|
+
5. Output Format:
|
|
230
|
+
- Wrap the output in a standard Markdown code block: \`\`\`mermaid
|
|
231
|
+
- Include a brief H1 header "# VibeGuard Architecture Diagram" before the code block
|
|
232
|
+
- Add a short "Legend" section after the diagram explaining symbols and color scheme
|
|
233
|
+
|
|
234
|
+
# OUTPUT
|
|
235
|
+
Return ONLY the complete Markdown content for DIAGRAM.md, including:
|
|
236
|
+
- H1 header
|
|
237
|
+
- Mermaid code block with valid syntax
|
|
238
|
+
- Legend section
|
|
239
|
+
|
|
240
|
+
Do not include any explanations outside the Markdown format.`;
|
|
241
|
+
const systemPrompt = `You are an expert at creating Mermaid.js diagrams for software architecture. You always generate valid, well-structured diagrams that follow Mermaid syntax rules strictly.`;
|
|
242
|
+
try {
|
|
243
|
+
const diagramContent = await generateSummary(prompt, systemPrompt, {
|
|
244
|
+
thinkingLevel: 'pro',
|
|
245
|
+
maxTokens: 10000,
|
|
246
|
+
feature: 'Librarian',
|
|
247
|
+
});
|
|
248
|
+
// Validate Mermaid syntax
|
|
249
|
+
const validation = validateMermaidSyntax(diagramContent);
|
|
250
|
+
let finalContent = diagramContent;
|
|
251
|
+
if (!validation.valid) {
|
|
252
|
+
// Try to fix common issues and regenerate
|
|
253
|
+
const fixPrompt = `The previous Mermaid diagram had syntax errors: ${validation.errors.join(', ')}.
|
|
254
|
+
|
|
255
|
+
Please regenerate the diagram with these fixes:
|
|
256
|
+
${diagramContent}
|
|
257
|
+
|
|
258
|
+
CRITICAL REQUIREMENTS:
|
|
259
|
+
1. Ensure all brackets are closed and all subgraphs have matching "end" statements.
|
|
260
|
+
2. MUST include color styling with classDef:
|
|
261
|
+
- classDef ExternalServices fill:#e1f5fe,stroke:#01579b,stroke-width:2px
|
|
262
|
+
- classDef InternalLogic fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
|
|
263
|
+
- classDef PersistenceFiles fill:#fff8e1,stroke:#f57f17,stroke-width:2px
|
|
264
|
+
3. Apply classes to subgraphs: "External Services" subgraph gets ExternalServices class, "Internal Logic" subgraph gets InternalLogic class.
|
|
265
|
+
4. Apply PersistenceFiles class to File System, Git Repository, or any persistence-related nodes.`;
|
|
266
|
+
const fixedContent = await generateSummary(fixPrompt, systemPrompt, {
|
|
267
|
+
thinkingLevel: 'pro',
|
|
268
|
+
maxTokens: 10000,
|
|
269
|
+
feature: 'Librarian',
|
|
270
|
+
});
|
|
271
|
+
const fixedValidation = validateMermaidSyntax(fixedContent);
|
|
272
|
+
if (fixedValidation.valid) {
|
|
273
|
+
finalContent = fixedContent;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Save the diagram
|
|
277
|
+
const diagramPath = path.join(repoPath, 'DIAGRAM.md');
|
|
278
|
+
await fs.writeFile(diagramPath, finalContent, 'utf-8');
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
console.error('Error generating diagram:', error.message || error);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Parse entries from the Recent Decisions section
|
|
286
|
+
* Returns an array of entry strings (each entry may span multiple lines)
|
|
287
|
+
*/
|
|
288
|
+
function parseRecentDecisionsEntries(memoryContent) {
|
|
289
|
+
const lines = memoryContent.split('\n');
|
|
290
|
+
const entries = [];
|
|
291
|
+
let inRecentDecisions = false;
|
|
292
|
+
let currentEntry = [];
|
|
293
|
+
for (let i = 0; i < lines.length; i++) {
|
|
294
|
+
const line = lines[i];
|
|
295
|
+
const trimmedLine = line.trim();
|
|
296
|
+
// Check if we've found the Recent Decisions section
|
|
297
|
+
if (trimmedLine.match(/^##\s+Recent\s+Decisions/i)) {
|
|
298
|
+
inRecentDecisions = true;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// If we're in the section and hit another ## header, we're done
|
|
302
|
+
if (inRecentDecisions && trimmedLine.match(/^##\s+/)) {
|
|
303
|
+
// Save last entry if exists
|
|
304
|
+
if (currentEntry.length > 0) {
|
|
305
|
+
entries.push(currentEntry.join('\n').trim());
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
// If we're in the section, collect entries
|
|
310
|
+
if (inRecentDecisions) {
|
|
311
|
+
// Detect entry start: lines starting with "- **" or "**" (bullet point format)
|
|
312
|
+
const isEntryStart = /^\s*[-*]\s+\*\*/.test(line) || /^\s*\*\*/.test(line);
|
|
313
|
+
if (isEntryStart) {
|
|
314
|
+
// Save previous entry if exists
|
|
315
|
+
if (currentEntry.length > 0) {
|
|
316
|
+
entries.push(currentEntry.join('\n').trim());
|
|
317
|
+
}
|
|
318
|
+
// Start new entry
|
|
319
|
+
currentEntry = [line];
|
|
320
|
+
}
|
|
321
|
+
else if (currentEntry.length > 0 || trimmedLine.length > 0) {
|
|
322
|
+
// Continue current entry (or start if first non-empty line after header)
|
|
323
|
+
if (trimmedLine.length > 0 || currentEntry.length > 0) {
|
|
324
|
+
currentEntry.push(line);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Save last entry if exists
|
|
330
|
+
if (inRecentDecisions && currentEntry.length > 0) {
|
|
331
|
+
entries.push(currentEntry.join('\n').trim());
|
|
332
|
+
}
|
|
333
|
+
return entries;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Remove the oldest N entries from Recent Decisions section
|
|
337
|
+
* Returns the modified content
|
|
338
|
+
*/
|
|
339
|
+
function removeOldestDecisions(memoryContent, countToRemove) {
|
|
340
|
+
const lines = memoryContent.split('\n');
|
|
341
|
+
const result = [];
|
|
342
|
+
let inRecentDecisions = false;
|
|
343
|
+
let entryCount = 0;
|
|
344
|
+
let skipCount = 0;
|
|
345
|
+
let currentEntry = [];
|
|
346
|
+
let shouldSkipCurrentEntry = false;
|
|
347
|
+
for (let i = 0; i < lines.length; i++) {
|
|
348
|
+
const line = lines[i];
|
|
349
|
+
const trimmedLine = line.trim();
|
|
350
|
+
// Check if we've found the Recent Decisions section
|
|
351
|
+
if (trimmedLine.match(/^##\s+Recent\s+Decisions/i)) {
|
|
352
|
+
inRecentDecisions = true;
|
|
353
|
+
result.push(line);
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
// If we're in the section and hit another ## header, we're done
|
|
357
|
+
if (inRecentDecisions && trimmedLine.match(/^##\s+/)) {
|
|
358
|
+
// Don't add skipped entry
|
|
359
|
+
result.push(line);
|
|
360
|
+
inRecentDecisions = false;
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
// If we're in the section, process entries
|
|
364
|
+
if (inRecentDecisions) {
|
|
365
|
+
const isEntryStart = /^\s*[-*]\s+\*\*/.test(line) || /^\s*\*\*/.test(line);
|
|
366
|
+
if (isEntryStart) {
|
|
367
|
+
// Process previous entry
|
|
368
|
+
if (currentEntry.length > 0) {
|
|
369
|
+
if (!shouldSkipCurrentEntry) {
|
|
370
|
+
result.push(...currentEntry);
|
|
371
|
+
}
|
|
372
|
+
currentEntry = [];
|
|
373
|
+
}
|
|
374
|
+
// Determine if we should skip this entry
|
|
375
|
+
entryCount++;
|
|
376
|
+
shouldSkipCurrentEntry = skipCount < countToRemove;
|
|
377
|
+
if (shouldSkipCurrentEntry) {
|
|
378
|
+
skipCount++;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
currentEntry.push(line);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Continue current entry
|
|
386
|
+
if (!shouldSkipCurrentEntry) {
|
|
387
|
+
currentEntry.push(line);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
// Not in Recent Decisions section, copy line as-is
|
|
393
|
+
result.push(line);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// Handle last entry if exists
|
|
397
|
+
if (inRecentDecisions && currentEntry.length > 0 && !shouldSkipCurrentEntry) {
|
|
398
|
+
result.push(...currentEntry);
|
|
399
|
+
}
|
|
400
|
+
return result.join('\n');
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Perform memory compaction: summarize oldest 10 entries and move to Legacy Context
|
|
404
|
+
*/
|
|
405
|
+
async function performMemoryCompaction(memoryContent) {
|
|
406
|
+
// Extract all Recent Decisions entries
|
|
407
|
+
const entries = parseRecentDecisionsEntries(memoryContent);
|
|
408
|
+
if (entries.length <= 10) {
|
|
409
|
+
return memoryContent; // Not enough entries to compact
|
|
410
|
+
}
|
|
411
|
+
// Get oldest 10 entries
|
|
412
|
+
const oldestEntries = entries.slice(0, 10);
|
|
413
|
+
const entriesText = oldestEntries.join('\n\n');
|
|
414
|
+
// Summarize using Gemini
|
|
415
|
+
const today = new Date().toLocaleDateString();
|
|
416
|
+
const summaryPrompt = `Summarize these 10 technical decisions into 3 concise bullet points for a section called 'Legacy Context'. Each bullet should capture the essential architectural or design decision, not individual file changes.
|
|
417
|
+
|
|
418
|
+
Decisions to summarize:
|
|
419
|
+
${entriesText}
|
|
420
|
+
|
|
421
|
+
Requirements:
|
|
422
|
+
- Output exactly 3 bullet points
|
|
423
|
+
- Each bullet should start with "- "
|
|
424
|
+
- Focus on architectural intent and "why" decisions were made
|
|
425
|
+
- Keep each bullet concise (1-2 sentences max)
|
|
426
|
+
- Use today's date (${today}) for any date references
|
|
427
|
+
|
|
428
|
+
Output only the 3 bullet points, no additional text.`;
|
|
429
|
+
const systemPrompt = `You are an expert at summarizing technical decisions. You create concise, high-density summaries that preserve architectural intent.`;
|
|
430
|
+
try {
|
|
431
|
+
const summary = await generateSummary(summaryPrompt, systemPrompt, {
|
|
432
|
+
thinkingLevel: 'pro',
|
|
433
|
+
maxTokens: 10000,
|
|
434
|
+
feature: 'Librarian',
|
|
435
|
+
});
|
|
436
|
+
// Extract bullet points from summary (clean up any markdown formatting)
|
|
437
|
+
let legacyBullets = summary.trim();
|
|
438
|
+
// Ensure each line starts with "- " and is a bullet point
|
|
439
|
+
const bulletLines = legacyBullets
|
|
440
|
+
.split('\n')
|
|
441
|
+
.map(line => line.trim())
|
|
442
|
+
.filter(line => line.length > 0)
|
|
443
|
+
.map(line => {
|
|
444
|
+
// If line doesn't start with "- ", add it
|
|
445
|
+
if (!line.startsWith('- ')) {
|
|
446
|
+
// Remove any existing markdown list markers
|
|
447
|
+
line = line.replace(/^[-*]\s*/, '');
|
|
448
|
+
return `- ${line}`;
|
|
449
|
+
}
|
|
450
|
+
return line;
|
|
451
|
+
});
|
|
452
|
+
// Ensure we have exactly 3 bullets (take first 3 if more)
|
|
453
|
+
const finalBullets = bulletLines.slice(0, 3).join('\n');
|
|
454
|
+
// Remove oldest 10 entries from Recent Decisions
|
|
455
|
+
let updatedContent = removeOldestDecisions(memoryContent, 10);
|
|
456
|
+
// Append to Legacy Context section (or create it)
|
|
457
|
+
// Check if Legacy Context section exists
|
|
458
|
+
const hasLegacySection = /^##\s+Legacy\s+Context/i.test(updatedContent);
|
|
459
|
+
if (hasLegacySection) {
|
|
460
|
+
// First, write the content with removed entries
|
|
461
|
+
await memoryManager.writeMemory(updatedContent);
|
|
462
|
+
// Then append to existing Legacy Context section
|
|
463
|
+
await memoryManager.appendToSection('Legacy Context', finalBullets);
|
|
464
|
+
// Re-read to get final updated content
|
|
465
|
+
updatedContent = await memoryManager.readMemory();
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
// Create new Legacy Context section
|
|
469
|
+
const lines = updatedContent.split('\n');
|
|
470
|
+
// Find a good place to insert (after Active Tech Debt or at the end)
|
|
471
|
+
let insertIndex = lines.length;
|
|
472
|
+
// Look for Active Tech Debt section
|
|
473
|
+
for (let i = 0; i < lines.length; i++) {
|
|
474
|
+
if (lines[i].match(/^##\s+Active\s+Tech\s+Debt/i)) {
|
|
475
|
+
// Find the end of this section
|
|
476
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
477
|
+
if (lines[j].match(/^##\s+/)) {
|
|
478
|
+
insertIndex = j;
|
|
479
|
+
break;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
// Insert Legacy Context section
|
|
486
|
+
const beforeSection = lines.slice(0, insertIndex).join('\n');
|
|
487
|
+
const afterSection = lines.slice(insertIndex).join('\n');
|
|
488
|
+
updatedContent = beforeSection +
|
|
489
|
+
(beforeSection.endsWith('\n') ? '' : '\n') +
|
|
490
|
+
'\n## Legacy Context\n\n' +
|
|
491
|
+
finalBullets + '\n' +
|
|
492
|
+
(afterSection.startsWith('\n') ? '' : '\n') +
|
|
493
|
+
afterSection;
|
|
494
|
+
// Write the updated content (with removed entries and new Legacy Context section)
|
|
495
|
+
await memoryManager.writeMemory(updatedContent);
|
|
496
|
+
updatedContent = await memoryManager.readMemory();
|
|
497
|
+
}
|
|
498
|
+
return updatedContent;
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
// If summarization fails, log error but don't fail the update
|
|
502
|
+
console.error('Librarian: Error during memory compaction:', error.message || error);
|
|
503
|
+
return memoryContent; // Return original content if compaction fails
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Parse the Pinned Files section from PROJECT_MEMORY.md
|
|
508
|
+
* Returns an array of file paths, or null if section doesn't exist
|
|
509
|
+
*/
|
|
510
|
+
function parsePinnedFiles(memoryContent) {
|
|
511
|
+
const lines = memoryContent.split('\n');
|
|
512
|
+
let inPinnedSection = false;
|
|
513
|
+
const filePaths = [];
|
|
514
|
+
for (const line of lines) {
|
|
515
|
+
const trimmedLine = line.trim();
|
|
516
|
+
// Check if we've found the Pinned Files section
|
|
517
|
+
if (trimmedLine === '## Pinned Files') {
|
|
518
|
+
inPinnedSection = true;
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
// If we're in the section and hit another ## header, we're done
|
|
522
|
+
if (inPinnedSection && trimmedLine.startsWith('## ')) {
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
// If we're in the section, collect non-empty lines as file paths
|
|
526
|
+
if (inPinnedSection) {
|
|
527
|
+
// Skip empty lines
|
|
528
|
+
if (trimmedLine.length === 0) {
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
// Extract path (remove list markers if present)
|
|
532
|
+
const filePath = trimmedLine.replace(/^[-*]\s+/, '').trim();
|
|
533
|
+
if (filePath.length > 0) {
|
|
534
|
+
filePaths.push(filePath);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return inPinnedSection ? filePaths : null;
|
|
539
|
+
}
|
|
540
|
+
// Register the read_project_memory tool using the underlying Server instance
|
|
541
|
+
server.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
542
|
+
return {
|
|
543
|
+
tools: [
|
|
544
|
+
{
|
|
545
|
+
name: 'read_project_memory',
|
|
546
|
+
description: "Reads the current PROJECT_MEMORY.md file to refresh knowledge of the project's architecture, tech stack, and recent decisions. Returns the complete content of the memory file.",
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: 'object',
|
|
549
|
+
properties: {},
|
|
550
|
+
required: [],
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
name: 'update_project_memory',
|
|
555
|
+
description: "Updates the PROJECT_MEMORY.md file by appending content to a specific section. If the section exists, appends the new content under that header. If not, appends it to the end of the file. Uses atomic writes for safety.",
|
|
556
|
+
inputSchema: {
|
|
557
|
+
type: 'object',
|
|
558
|
+
properties: {
|
|
559
|
+
content: {
|
|
560
|
+
type: 'string',
|
|
561
|
+
description: 'The text content to add to the section.',
|
|
562
|
+
},
|
|
563
|
+
section: {
|
|
564
|
+
type: 'string',
|
|
565
|
+
description: 'The section name where to add the content (e.g., "Recent Decisions", "Active Tech Debt"). Will match section headers like "## Recent Decisions".',
|
|
566
|
+
},
|
|
567
|
+
},
|
|
568
|
+
required: ['content', 'section'],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
{
|
|
572
|
+
name: 'get_core_context',
|
|
573
|
+
description: "Reads pinned files from PROJECT_MEMORY.md's '## Pinned Files' section and returns their contents in a structured Markdown format. If the section doesn't exist, it will be created with default files (PROJECT_MEMORY.md and package.json).",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: 'object',
|
|
576
|
+
properties: {},
|
|
577
|
+
required: [],
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
};
|
|
582
|
+
});
|
|
583
|
+
// Handle tool execution using the underlying Server instance
|
|
584
|
+
// Add explicit type to silence TS7006
|
|
585
|
+
server.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
586
|
+
const { name, arguments: args } = request.params;
|
|
587
|
+
if (name === 'read_project_memory') {
|
|
588
|
+
try {
|
|
589
|
+
// Read the PROJECT_MEMORY.md file
|
|
590
|
+
const memoryContent = await memoryManager.readMemory();
|
|
591
|
+
if (!memoryContent || memoryContent.trim().length === 0) {
|
|
592
|
+
return {
|
|
593
|
+
content: [
|
|
594
|
+
{
|
|
595
|
+
type: 'text',
|
|
596
|
+
text: 'PROJECT_MEMORY.md is empty or does not exist. Run `vibeguard init` to create it.',
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
return {
|
|
602
|
+
content: [
|
|
603
|
+
{
|
|
604
|
+
type: 'text',
|
|
605
|
+
text: memoryContent,
|
|
606
|
+
},
|
|
607
|
+
],
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
catch (error) {
|
|
611
|
+
return {
|
|
612
|
+
content: [
|
|
613
|
+
{
|
|
614
|
+
type: 'text',
|
|
615
|
+
text: `Error reading PROJECT_MEMORY.md: ${error.message || error}`,
|
|
616
|
+
},
|
|
617
|
+
],
|
|
618
|
+
isError: true,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (name === 'update_project_memory') {
|
|
623
|
+
try {
|
|
624
|
+
// Extract content and section parameters
|
|
625
|
+
const content = args?.content;
|
|
626
|
+
const section = args?.section;
|
|
627
|
+
if (!content || typeof content !== 'string') {
|
|
628
|
+
return {
|
|
629
|
+
content: [
|
|
630
|
+
{
|
|
631
|
+
type: 'text',
|
|
632
|
+
text: 'Error: content parameter is required and must be a string.',
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
isError: true,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
if (!section || typeof section !== 'string') {
|
|
639
|
+
return {
|
|
640
|
+
content: [
|
|
641
|
+
{
|
|
642
|
+
type: 'text',
|
|
643
|
+
text: 'Error: section parameter is required and must be a string.',
|
|
644
|
+
},
|
|
645
|
+
],
|
|
646
|
+
isError: true,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
// Memory Compaction Check: If updating "Recent Decisions", check if compaction is needed
|
|
650
|
+
const normalizedSection = section.trim().toLowerCase();
|
|
651
|
+
if (normalizedSection === 'recent decisions' || normalizedSection === 'recent decisions (the "why")') {
|
|
652
|
+
// Read current memory to check entry count
|
|
653
|
+
const currentMemory = await memoryManager.readMemory();
|
|
654
|
+
const entries = parseRecentDecisionsEntries(currentMemory);
|
|
655
|
+
// If we have more than 15 entries, trigger compaction
|
|
656
|
+
if (entries.length > 15) {
|
|
657
|
+
console.error('Librarian: Compacting 10 old decisions into Legacy Context to save tokens.');
|
|
658
|
+
await performMemoryCompaction(currentMemory);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
// Use MemoryManager.appendToSection() which handles:
|
|
662
|
+
// - Finding the section or appending to end
|
|
663
|
+
// - Atomic writes via write-file-atomic
|
|
664
|
+
// - Compression if needed
|
|
665
|
+
// - Auto-staging in Git
|
|
666
|
+
await memoryManager.appendToSection(section, content);
|
|
667
|
+
return {
|
|
668
|
+
content: [
|
|
669
|
+
{
|
|
670
|
+
type: 'text',
|
|
671
|
+
text: `Project Memory updated successfully. Content appended to section: ${section}`,
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
catch (error) {
|
|
677
|
+
return {
|
|
678
|
+
content: [
|
|
679
|
+
{
|
|
680
|
+
type: 'text',
|
|
681
|
+
text: `Error updating PROJECT_MEMORY.md: ${error.message || error}`,
|
|
682
|
+
},
|
|
683
|
+
],
|
|
684
|
+
isError: true,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (name === 'get_core_context') {
|
|
689
|
+
try {
|
|
690
|
+
// Staleness Check: Compare src/ mtime with DIAGRAM.md mtime
|
|
691
|
+
const srcPath = path.join(repoPath, 'src');
|
|
692
|
+
const diagramPath = path.join(repoPath, 'DIAGRAM.md');
|
|
693
|
+
let isStale = false;
|
|
694
|
+
let stalenessWarning = '';
|
|
695
|
+
try {
|
|
696
|
+
// Get latest mtime from src/ directory
|
|
697
|
+
const srcLatestMTime = await getLatestMTime(srcPath);
|
|
698
|
+
// Get mtime of DIAGRAM.md if it exists
|
|
699
|
+
let diagramMTime = null;
|
|
700
|
+
try {
|
|
701
|
+
const diagramStats = await fs.stat(diagramPath);
|
|
702
|
+
diagramMTime = diagramStats.mtime;
|
|
703
|
+
}
|
|
704
|
+
catch (error) {
|
|
705
|
+
// DIAGRAM.md doesn't exist - consider it stale
|
|
706
|
+
if (srcLatestMTime) {
|
|
707
|
+
isStale = true;
|
|
708
|
+
stalenessWarning = 'CONTEXT_STALE: The architecture diagram is out of date.';
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
// Compare timestamps if both exist
|
|
712
|
+
if (srcLatestMTime && diagramMTime && srcLatestMTime > diagramMTime) {
|
|
713
|
+
isStale = true;
|
|
714
|
+
stalenessWarning = 'CONTEXT_STALE: The architecture diagram is out of date.';
|
|
715
|
+
}
|
|
716
|
+
// If stale, trigger visualization automatically
|
|
717
|
+
if (isStale) {
|
|
718
|
+
console.error('Context is stale. Regenerating DIAGRAM.md...');
|
|
719
|
+
await generateDiagram();
|
|
720
|
+
// Re-check after generation
|
|
721
|
+
try {
|
|
722
|
+
const newDiagramStats = await fs.stat(diagramPath);
|
|
723
|
+
const newSrcMTime = await getLatestMTime(srcPath);
|
|
724
|
+
if (newSrcMTime && newSrcMTime > newDiagramStats.mtime) {
|
|
725
|
+
// Still stale after generation (files changed during generation)
|
|
726
|
+
stalenessWarning = 'CONTEXT_STALE: The architecture diagram is out of date.';
|
|
727
|
+
}
|
|
728
|
+
else {
|
|
729
|
+
// Successfully updated
|
|
730
|
+
stalenessWarning = '';
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
// Diagram generation may have failed, keep warning
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
catch (error) {
|
|
739
|
+
// If we can't check staleness, continue without warning
|
|
740
|
+
console.error('Error checking staleness:', error);
|
|
741
|
+
}
|
|
742
|
+
// Read PROJECT_MEMORY.md
|
|
743
|
+
let memoryContent = await memoryManager.readMemory();
|
|
744
|
+
if (!memoryContent || memoryContent.trim().length === 0) {
|
|
745
|
+
return {
|
|
746
|
+
content: [
|
|
747
|
+
{
|
|
748
|
+
type: 'text',
|
|
749
|
+
text: 'PROJECT_MEMORY.md is empty or does not exist. Run `vibeguard init` to create it.',
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
isError: true,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
// Parse pinned files section
|
|
756
|
+
let filePaths = parsePinnedFiles(memoryContent);
|
|
757
|
+
// If section doesn't exist, create it with defaults
|
|
758
|
+
if (filePaths === null) {
|
|
759
|
+
const defaultFiles = 'PROJECT_MEMORY.md\npackage.json';
|
|
760
|
+
await memoryManager.appendToSection('Pinned Files', defaultFiles);
|
|
761
|
+
// Re-read memory to get updated content
|
|
762
|
+
memoryContent = await memoryManager.readMemory();
|
|
763
|
+
filePaths = parsePinnedFiles(memoryContent);
|
|
764
|
+
// If still null after creation, use defaults directly
|
|
765
|
+
if (filePaths === null) {
|
|
766
|
+
filePaths = ['PROJECT_MEMORY.md', 'package.json'];
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// If no files found, use defaults
|
|
770
|
+
if (filePaths.length === 0) {
|
|
771
|
+
filePaths = ['PROJECT_MEMORY.md', 'package.json'];
|
|
772
|
+
}
|
|
773
|
+
// Check if DIAGRAM.md exists and prepend it
|
|
774
|
+
let diagramContent = '';
|
|
775
|
+
try {
|
|
776
|
+
diagramContent = await fs.readFile(diagramPath, 'utf-8');
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
// DIAGRAM.md doesn't exist - that's fine, continue without it
|
|
780
|
+
}
|
|
781
|
+
// Read pinned files and build output
|
|
782
|
+
const outputParts = ['# Pinned Project Context', ''];
|
|
783
|
+
// Add staleness warning if present
|
|
784
|
+
if (stalenessWarning) {
|
|
785
|
+
outputParts.push(`**⚠️ ${stalenessWarning}**`, '');
|
|
786
|
+
outputParts.push('');
|
|
787
|
+
}
|
|
788
|
+
// Prepend DIAGRAM.md if it exists
|
|
789
|
+
if (diagramContent && diagramContent.trim().length > 0) {
|
|
790
|
+
outputParts.push('## Architecture Diagram', '');
|
|
791
|
+
outputParts.push(diagramContent);
|
|
792
|
+
outputParts.push('');
|
|
793
|
+
outputParts.push('---');
|
|
794
|
+
outputParts.push('');
|
|
795
|
+
}
|
|
796
|
+
const errors = [];
|
|
797
|
+
for (const filePath of filePaths) {
|
|
798
|
+
try {
|
|
799
|
+
const fullPath = path.join(repoPath, filePath);
|
|
800
|
+
const fileContent = await fs.readFile(fullPath, 'utf-8');
|
|
801
|
+
outputParts.push(`## File: ${filePath}`);
|
|
802
|
+
outputParts.push('');
|
|
803
|
+
outputParts.push(fileContent);
|
|
804
|
+
outputParts.push('');
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
// Handle file not found gracefully
|
|
808
|
+
if (error.code === 'ENOENT') {
|
|
809
|
+
errors.push(`File not found: ${filePath}`);
|
|
810
|
+
// Still add the section but with a note
|
|
811
|
+
outputParts.push(`## File: ${filePath}`);
|
|
812
|
+
outputParts.push('');
|
|
813
|
+
outputParts.push(`*File not found: ${filePath}*`);
|
|
814
|
+
outputParts.push('');
|
|
815
|
+
}
|
|
816
|
+
else {
|
|
817
|
+
errors.push(`Error reading ${filePath}: ${error.message || error}`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
// Add errors at the end if any
|
|
822
|
+
if (errors.length > 0) {
|
|
823
|
+
outputParts.push('---');
|
|
824
|
+
outputParts.push('');
|
|
825
|
+
outputParts.push('## Errors');
|
|
826
|
+
outputParts.push('');
|
|
827
|
+
for (const error of errors) {
|
|
828
|
+
outputParts.push(`- ${error}`);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
const output = outputParts.join('\n');
|
|
832
|
+
return {
|
|
833
|
+
content: [
|
|
834
|
+
{
|
|
835
|
+
type: 'text',
|
|
836
|
+
text: output,
|
|
837
|
+
},
|
|
838
|
+
],
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
catch (error) {
|
|
842
|
+
return {
|
|
843
|
+
content: [
|
|
844
|
+
{
|
|
845
|
+
type: 'text',
|
|
846
|
+
text: `Error getting core context: ${error.message || error}`,
|
|
847
|
+
},
|
|
848
|
+
],
|
|
849
|
+
isError: true,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
854
|
+
});
|
|
855
|
+
// Start the server
|
|
856
|
+
async function main() {
|
|
857
|
+
const transport = new StdioServerTransport();
|
|
858
|
+
// Connect the server to the transport
|
|
859
|
+
await server.connect(transport);
|
|
860
|
+
// Log to stderr (stdout is used for MCP protocol)
|
|
861
|
+
console.error('VibeGuard MCP Server started');
|
|
862
|
+
console.error('Tools available: read_project_memory, update_project_memory, get_core_context');
|
|
863
|
+
console.error('Waiting for MCP client connections...');
|
|
864
|
+
}
|
|
865
|
+
// Handle errors
|
|
866
|
+
process.on('SIGINT', async () => {
|
|
867
|
+
console.error('\nShutting down MCP server...');
|
|
868
|
+
process.exit(0);
|
|
869
|
+
});
|
|
870
|
+
process.on('SIGTERM', async () => {
|
|
871
|
+
console.error('\nShutting down MCP server...');
|
|
872
|
+
process.exit(0);
|
|
873
|
+
});
|
|
874
|
+
// Start the server
|
|
875
|
+
main().catch((error) => {
|
|
876
|
+
console.error('Fatal error starting MCP server:', error);
|
|
877
|
+
process.exit(1);
|
|
878
|
+
});
|
|
879
|
+
//# sourceMappingURL=mcp-server.js.map
|