Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

mermaidGenerator.ts11.4 kB
/** * Mermaid 다이어그램 생성 유틸리티 * 의존성 그래프, 플로우차트, 클래스 다이어그램 등 생성 */ import { CodeAnalysis, ClassInfo, FunctionInfo, ImportInfo } from './astParser.js'; export interface DiagramOptions { direction?: 'TB' | 'BT' | 'LR' | 'RL'; theme?: 'default' | 'dark' | 'forest' | 'neutral'; showPrivate?: boolean; maxNodes?: number; } // 의존성 그래프 생성 export function generateDependencyGraph( analyses: { filename: string; analysis: CodeAnalysis }[], options: DiagramOptions = {} ): string { const { direction = 'LR', maxNodes = 50 } = options; const lines: string[] = [`flowchart ${direction}`]; const nodes = new Set<string>(); const edges: string[] = []; for (const { filename, analysis } of analyses) { const nodeName = sanitizeNodeName(filename); nodes.add(nodeName); for (const imp of analysis.imports) { if (!imp.source.startsWith('.')) continue; // 외부 패키지 제외 const targetName = sanitizeNodeName(imp.source); nodes.add(targetName); edges.push(` ${nodeName} --> ${targetName}`); } if (nodes.size >= maxNodes) break; } // 노드 정의 for (const node of nodes) { lines.push(` ${node}[${node}]`); } // 엣지 추가 lines.push(...edges); return lines.join('\n'); } // 클래스 다이어그램 생성 export function generateClassDiagram( classes: ClassInfo[], options: DiagramOptions = {} ): string { const { showPrivate = false } = options; const lines: string[] = ['classDiagram']; for (const cls of classes) { // 클래스 정의 lines.push(` class ${cls.name} {`); // 속성 for (const prop of cls.properties) { if (!showPrivate && prop.visibility === 'private') continue; const visibility = prop.visibility === 'private' ? '-' : prop.visibility === 'protected' ? '#' : '+'; const staticPrefix = prop.static ? '$' : ''; lines.push(` ${visibility}${staticPrefix}${prop.name}${prop.type ? `: ${prop.type}` : ''}`); } // 메서드 for (const method of cls.methods) { if (!showPrivate && method.name.startsWith('_')) continue; const asyncPrefix = method.async ? '<<async>> ' : ''; lines.push(` +${asyncPrefix}${method.name}()`); } lines.push(' }'); // 상속 관계 if (cls.extends) { lines.push(` ${cls.extends} <|-- ${cls.name}`); } // 구현 관계 if (cls.implements) { for (const impl of cls.implements) { lines.push(` ${impl} <|.. ${cls.name}`); } } } return lines.join('\n'); } // 플로우차트 생성 (함수 호출 흐름) export function generateFlowchart( functions: FunctionInfo[], options: DiagramOptions = {} ): string { const { direction = 'TB' } = options; const lines: string[] = [`flowchart ${direction}`]; for (let i = 0; i < functions.length; i++) { const func = functions[i]; const nodeName = sanitizeNodeName(func.name); const shape = func.async ? `${nodeName}{{${func.name}}}` : `${nodeName}[${func.name}]`; lines.push(` ${shape}`); // 연결 (순차적) if (i > 0) { const prevName = sanitizeNodeName(functions[i - 1].name); lines.push(` ${prevName} --> ${nodeName}`); } } return lines.join('\n'); } // 시퀀스 다이어그램 생성 export function generateSequenceDiagram( participants: string[], interactions: { from: string; to: string; message: string; type?: 'sync' | 'async' | 'return' }[] ): string { const lines: string[] = ['sequenceDiagram']; // 참가자 정의 for (const participant of participants) { lines.push(` participant ${sanitizeNodeName(participant)} as ${participant}`); } lines.push(''); // 상호작용 for (const { from, to, message, type = 'sync' } of interactions) { const fromName = sanitizeNodeName(from); const toName = sanitizeNodeName(to); switch (type) { case 'async': lines.push(` ${fromName} ->> ${toName}: ${message}`); break; case 'return': lines.push(` ${fromName} -->> ${toName}: ${message}`); break; default: lines.push(` ${fromName} -> ${toName}: ${message}`); } } return lines.join('\n'); } // 아키텍처 다이어그램 생성 export function generateArchitectureDiagram( components: { name: string; type: 'service' | 'database' | 'api' | 'client' | 'queue' }[], connections: { from: string; to: string; label?: string }[], options: DiagramOptions = {} ): string { const { direction = 'TB' } = options; const lines: string[] = [`flowchart ${direction}`]; // 서브그래프로 레이어 구분 const services = components.filter(c => c.type === 'service'); const databases = components.filter(c => c.type === 'database'); const apis = components.filter(c => c.type === 'api'); const clients = components.filter(c => c.type === 'client'); const queues = components.filter(c => c.type === 'queue'); if (clients.length > 0) { lines.push(' subgraph Clients'); for (const c of clients) { lines.push(` ${sanitizeNodeName(c.name)}[${c.name}]`); } lines.push(' end'); } if (apis.length > 0) { lines.push(' subgraph APIs'); for (const c of apis) { lines.push(` ${sanitizeNodeName(c.name)}{{${c.name}}}`); } lines.push(' end'); } if (services.length > 0) { lines.push(' subgraph Services'); for (const c of services) { lines.push(` ${sanitizeNodeName(c.name)}[${c.name}]`); } lines.push(' end'); } if (queues.length > 0) { lines.push(' subgraph Queues'); for (const c of queues) { lines.push(` ${sanitizeNodeName(c.name)}>>${c.name}]`); } lines.push(' end'); } if (databases.length > 0) { lines.push(' subgraph Databases'); for (const c of databases) { lines.push(` ${sanitizeNodeName(c.name)}[(${c.name})]`); } lines.push(' end'); } // 연결 for (const conn of connections) { const from = sanitizeNodeName(conn.from); const to = sanitizeNodeName(conn.to); if (conn.label) { lines.push(` ${from} -->|${conn.label}| ${to}`); } else { lines.push(` ${from} --> ${to}`); } } return lines.join('\n'); } // 상태 다이어그램 생성 export function generateStateDiagram( states: string[], transitions: { from: string; to: string; trigger?: string }[] ): string { const lines: string[] = ['stateDiagram-v2']; // 시작 상태 if (states.length > 0) { lines.push(` [*] --> ${sanitizeNodeName(states[0])}`); } // 전환 for (const { from, to, trigger } of transitions) { const fromName = from === 'start' ? '[*]' : sanitizeNodeName(from); const toName = to === 'end' ? '[*]' : sanitizeNodeName(to); if (trigger) { lines.push(` ${fromName} --> ${toName}: ${trigger}`); } else { lines.push(` ${fromName} --> ${toName}`); } } return lines.join('\n'); } // 간트 차트 생성 export function generateGanttChart( title: string, tasks: { name: string; start: string; duration: string; section?: string }[] ): string { const lines: string[] = [ 'gantt', ` title ${title}`, ' dateFormat YYYY-MM-DD' ]; let currentSection = ''; for (const task of tasks) { if (task.section && task.section !== currentSection) { currentSection = task.section; lines.push(` section ${currentSection}`); } lines.push(` ${task.name}: ${task.start}, ${task.duration}`); } return lines.join('\n'); } // ER 다이어그램 생성 export function generateERDiagram( entities: { name: string; attributes: { name: string; type: string; pk?: boolean; fk?: boolean }[] }[], relationships: { from: string; to: string; type: 'one-to-one' | 'one-to-many' | 'many-to-many'; label?: string }[] ): string { const lines: string[] = ['erDiagram']; // 엔티티 정의 for (const entity of entities) { lines.push(` ${entity.name} {`); for (const attr of entity.attributes) { const pk = attr.pk ? ' PK' : ''; const fk = attr.fk ? ' FK' : ''; lines.push(` ${attr.type} ${attr.name}${pk}${fk}`); } lines.push(' }'); } // 관계 정의 const relationSymbols: Record<string, string> = { 'one-to-one': '||--||', 'one-to-many': '||--o{', 'many-to-many': '}o--o{' }; for (const rel of relationships) { const symbol = relationSymbols[rel.type]; if (rel.label) { lines.push(` ${rel.from} ${symbol} ${rel.to} : "${rel.label}"`); } else { lines.push(` ${rel.from} ${symbol} ${rel.to} : ""`); } } return lines.join('\n'); } // 파이 차트 생성 export function generatePieChart( title: string, data: { label: string; value: number }[] ): string { const lines: string[] = [ 'pie showData', ` title ${title}` ]; for (const item of data) { lines.push(` "${item.label}" : ${item.value}`); } return lines.join('\n'); } // 타임라인 생성 export function generateTimeline( title: string, events: { period: string; events: string[] }[] ): string { const lines: string[] = [ 'timeline', ` title ${title}` ]; for (const period of events) { lines.push(` ${period.period}`); for (const event of period.events) { lines.push(` : ${event}`); } } return lines.join('\n'); } // 노드 이름 정규화 (Mermaid 호환) function sanitizeNodeName(name: string): string { return name .replace(/[^a-zA-Z0-9_]/g, '_') .replace(/^_+|_+$/g, '') .replace(/_+/g, '_') || 'node'; } // 종합 다이어그램 생성 (코드 분석 결과로부터) export function generateDiagramsFromAnalysis( analysis: CodeAnalysis, filename: string ): { type: string; diagram: string }[] { const diagrams: { type: string; diagram: string }[] = []; // 클래스 다이어그램 if (analysis.classes.length > 0) { diagrams.push({ type: 'class', diagram: generateClassDiagram(analysis.classes) }); } // 의존성 다이어그램 if (analysis.imports.length > 0) { const depDiagram = generateDependencyDiagramFromImports(filename, analysis.imports); diagrams.push({ type: 'dependency', diagram: depDiagram }); } // 함수 플로우차트 if (analysis.functions.length > 0) { diagrams.push({ type: 'flowchart', diagram: generateFlowchart(analysis.functions) }); } // 언어별 통계 파이차트 diagrams.push({ type: 'stats', diagram: generatePieChart('Code Composition', [ { label: 'Functions', value: analysis.functions.length }, { label: 'Classes', value: analysis.classes.length }, { label: 'Imports', value: analysis.imports.length } ]) }); return diagrams; } function generateDependencyDiagramFromImports(filename: string, imports: ImportInfo[]): string { const lines: string[] = ['flowchart LR']; const mainNode = sanitizeNodeName(filename); lines.push(` ${mainNode}[${filename}]`); for (const imp of imports) { const importNode = sanitizeNodeName(imp.source); lines.push(` ${importNode}[${imp.source}]`); lines.push(` ${mainNode} --> ${importNode}`); } return lines.join('\n'); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/MUSE-CODE-SPACE/vibe-coding-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server