Skip to main content
Glama
reportGenerator.ts6.37 kB
/** * Service for generating weekly reports from session notes */ import type { SessionNote, SessionPattern, ComplexityLevel } from '../types/session.js'; export interface WeeklyReport { startDate: string; endDate: string; totalSessions: number; totalFiles: number; totalCommands: number; projectBreakdown: Record<string, number>; patternBreakdown: Record<SessionPattern, number>; complexityBreakdown: Record<ComplexityLevel, number>; sessions: SessionNote[]; } /** * Generate a weekly report from session notes */ export function generateWeeklyReport(notes: SessionNote[], weekStartDate?: Date): WeeklyReport { // Determine week boundaries const now = weekStartDate || new Date(); const startOfWeek = new Date(now); const dayOfWeek = now.getDay(); // Adjust so Monday is start of week (0=Sunday becomes 6 days back, 1=Monday becomes 0 days back) const daysToSubtract = dayOfWeek === 0 ? 6 : dayOfWeek - 1; startOfWeek.setDate(now.getDate() - daysToSubtract); // Start of week (Monday) startOfWeek.setHours(0, 0, 0, 0); const endOfWeek = new Date(startOfWeek); endOfWeek.setDate(startOfWeek.getDate() + 7); // Filter notes for this week const weekNotes = notes.filter(note => { const noteDate = new Date(note.timestamp); return noteDate >= startOfWeek && noteDate < endOfWeek; }); // Calculate statistics const totalFiles = weekNotes.reduce((sum, note) => sum + (note.fileChanges?.length || 0), 0 ); const totalCommands = weekNotes.reduce((sum, note) => sum + (note.commands?.length || 0), 0 ); // Project breakdown const projectBreakdown: Record<string, number> = {}; for (const note of weekNotes) { const project = note.projectName || 'Unknown'; projectBreakdown[project] = (projectBreakdown[project] || 0) + 1; } // Pattern breakdown const patternBreakdown: Record<SessionPattern, number> = { 'new-feature': 0, 'bug-fix': 0, 'refactoring': 0, 'documentation': 0, 'configuration': 0, 'testing': 0, 'mixed': 0, }; for (const note of weekNotes) { if (note.analysis?.pattern) { patternBreakdown[note.analysis.pattern]++; } } // Complexity breakdown const complexityBreakdown: Record<ComplexityLevel, number> = { 'simple': 0, 'moderate': 0, 'complex': 0, }; for (const note of weekNotes) { if (note.analysis?.complexity) { complexityBreakdown[note.analysis.complexity]++; } } return { startDate: startOfWeek.toISOString(), endDate: endOfWeek.toISOString(), totalSessions: weekNotes.length, totalFiles, totalCommands, projectBreakdown, patternBreakdown, complexityBreakdown, sessions: weekNotes, }; } /** * Format weekly report as markdown */ export function formatWeeklyReport(report: WeeklyReport): string { const lines: string[] = []; // Title const startDate = new Date(report.startDate).toLocaleDateString(); const endDate = new Date(report.endDate).toLocaleDateString(); lines.push(`# Weekly Coding Report`); lines.push(`## ${startDate} - ${endDate}`); lines.push(''); // Summary lines.push('## Summary'); lines.push(''); lines.push(`- **Total Sessions**: ${report.totalSessions}`); lines.push(`- **Files Changed**: ${report.totalFiles}`); lines.push(`- **Commands Executed**: ${report.totalCommands}`); lines.push(''); // Project breakdown if (Object.keys(report.projectBreakdown).length > 0) { lines.push('## Projects'); lines.push(''); const sortedProjects = Object.entries(report.projectBreakdown) .sort((a, b) => b[1] - a[1]); for (const [project, count] of sortedProjects) { lines.push(`- **${project}**: ${count} sessions`); } lines.push(''); } // Pattern breakdown lines.push('## Work Patterns'); lines.push(''); const patterns = Object.entries(report.patternBreakdown) .filter(([_, count]) => count > 0) .sort((a, b) => b[1] - a[1]); for (const [pattern, count] of patterns) { const emoji = getPatternEmoji(pattern as SessionPattern); lines.push(`- ${emoji} **${formatPatternName(pattern)}**: ${count} sessions`); } lines.push(''); // Complexity breakdown lines.push('## Complexity Distribution'); lines.push(''); for (const [complexity, count] of Object.entries(report.complexityBreakdown)) { if (count > 0) { const emoji = getComplexityEmoji(complexity as ComplexityLevel); lines.push(`- ${emoji} **${complexity.charAt(0).toUpperCase() + complexity.slice(1)}**: ${count} sessions`); } } lines.push(''); // Sessions list lines.push('## Sessions This Week'); lines.push(''); let sessionNumber = 1; for (const session of report.sessions) { // Format date as "Nov 30, 23:45" const date = new Date(session.timestamp); const dateStr = date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' }); const timeStr = date.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: false }); const shortDate = `${dateStr}, ${timeStr}`; // Get topic or fallback to summary const topic = session.topic || session.summary.substring(0, 50) + (session.summary.length > 50 ? '...' : ''); // Get project name const project = session.projectName || 'General'; // Format: "1. Topic - **Project** (Date Time)" lines.push(`${sessionNumber}. ${topic} - **${project}** (${shortDate})`); lines.push(` ${session.summary}`); // Indent summary for readability lines.push(''); sessionNumber++; } return lines.join('\n'); } function getPatternEmoji(pattern: SessionPattern): string { const emojis: Record<SessionPattern, string> = { 'new-feature': '🆕', 'bug-fix': '🐛', 'refactoring': '♻️', 'documentation': '📝', 'configuration': '⚙️', 'testing': '✅', 'mixed': '🔀', }; return emojis[pattern] || '📌'; } function getComplexityEmoji(complexity: ComplexityLevel): string { const emojis: Record<ComplexityLevel, string> = { 'simple': '🟢', 'moderate': '🟡', 'complex': '🔴', }; return emojis[complexity] || '⚪'; } function formatPatternName(pattern: string): string { return pattern.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' '); }

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/VoCoufi/second-brain-mcp'

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