Skip to main content
Glama
patternDetector.ts7.29 kB
/** * Service for detecting session patterns (new feature, bug fix, refactoring, etc.) */ import type { SessionNote, SessionPattern } from '../types/session.js'; interface PatternScore { pattern: SessionPattern; score: number; reasons: string[]; } /** * Detect the primary pattern of the session */ export function detectPattern(note: SessionNote): { pattern: SessionPattern; confidence: number } { const scores: PatternScore[] = [ analyzeNewFeature(note), analyzeBugFix(note), analyzeRefactoring(note), analyzeDocumentation(note), analyzeConfiguration(note), analyzeTesting(note), ]; // Sort by score scores.sort((a, b) => b.score - a.score); const topScore = scores[0].score; const secondScore = scores[1].score; // If top two scores are close, it's mixed if (topScore > 0 && secondScore > 0 && (topScore - secondScore) < 3) { return { pattern: 'mixed', confidence: 0.6 }; } // Calculate confidence (0-1) const maxPossibleScore = 10; const confidence = Math.min(topScore / maxPossibleScore, 1); return { pattern: scores[0].pattern, confidence: Math.max(confidence, 0.1), // Minimum 0.1 confidence }; } function analyzeNewFeature(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; // Check summary/topic for keywords const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('implement') || text.includes('add') || text.includes('create') || text.includes('new')) { score += 3; reasons.push('Contains implementation keywords'); } // Check file changes if (note.fileChanges) { const created = note.fileChanges.filter(f => f.type === 'created'); if (created.length >= 3) { score += 4; reasons.push(`Created ${created.length} new files`); } else if (created.length > 0) { score += 2; } } // Check tags if (note.tags?.some(t => t.includes('feature') || t.includes('new'))) { score += 2; reasons.push('Tagged as feature'); } return { pattern: 'new-feature', score, reasons }; } function analyzeBugFix(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('fix') || text.includes('bug') || text.includes('issue') || text.includes('error')) { score += 4; reasons.push('Contains bug fix keywords'); } // Bug fixes usually modify existing files if (note.fileChanges) { const modified = note.fileChanges.filter(f => f.type === 'modified'); const created = note.fileChanges.filter(f => f.type === 'created'); if (modified.length > created.length) { score += 3; reasons.push('Mostly modified existing files'); } } if (note.tags?.some(t => t.includes('bug') || t.includes('fix'))) { score += 2; reasons.push('Tagged as bug fix'); } return { pattern: 'bug-fix', score, reasons }; } function analyzeRefactoring(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('refactor') || text.includes('restructure') || text.includes('clean') || text.includes('improve')) { score += 4; reasons.push('Contains refactoring keywords'); } // Refactoring often involves many file modifications if (note.fileChanges) { const modified = note.fileChanges.filter(f => f.type === 'modified'); if (modified.length >= 4) { score += 3; reasons.push(`Modified ${modified.length} files`); } } if (note.tags?.some(t => t.includes('refactor'))) { score += 2; reasons.push('Tagged as refactoring'); } return { pattern: 'refactoring', score, reasons }; } function analyzeDocumentation(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('document') || text.includes('readme') || text.includes('docs') || text.includes('comment')) { score += 4; reasons.push('Contains documentation keywords'); } // Check for markdown files if (note.fileChanges) { const docFiles = note.fileChanges.filter(f => f.path.endsWith('.md') || f.path.endsWith('.mdx') || f.path.includes('README') ); if (docFiles.length > 0) { score += 4; reasons.push(`Modified ${docFiles.length} documentation files`); } } if (note.tags?.some(t => t.includes('doc') || t.includes('readme'))) { score += 2; reasons.push('Tagged as documentation'); } return { pattern: 'documentation', score, reasons }; } function analyzeConfiguration(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('config') || text.includes('setup') || text.includes('install') || text.includes('dependencies')) { score += 3; reasons.push('Contains configuration keywords'); } // Check for config files if (note.fileChanges) { const configFiles = note.fileChanges.filter(f => f.path.includes('package.json') || f.path.includes('tsconfig') || f.path.includes('config') || f.path.includes('.yml') || f.path.includes('.yaml') || f.path.includes('.json') || f.path.includes('.env') ); if (configFiles.length >= 2) { score += 4; reasons.push(`Modified ${configFiles.length} config files`); } else if (configFiles.length > 0) { score += 2; } } // Check for install/setup commands if (note.commands) { const setupCommands = note.commands.filter(c => c.command.includes('install') || c.command.includes('init') || c.command.includes('setup') ); if (setupCommands.length > 0) { score += 3; reasons.push('Ran setup commands'); } } if (note.tags?.some(t => t.includes('config') || t.includes('setup'))) { score += 2; reasons.push('Tagged as configuration'); } return { pattern: 'configuration', score, reasons }; } function analyzeTesting(note: SessionNote): PatternScore { let score = 0; const reasons: string[] = []; const text = `${note.summary} ${note.topic || ''}`.toLowerCase(); if (text.includes('test') || text.includes('spec')) { score += 4; reasons.push('Contains testing keywords'); } // Check for test files if (note.fileChanges) { const testFiles = note.fileChanges.filter(f => f.path.includes('.test.') || f.path.includes('.spec.') || f.path.includes('__tests__') || f.path.includes('/test/') ); if (testFiles.length > 0) { score += 4; reasons.push(`Modified ${testFiles.length} test files`); } } // Check for test commands if (note.commands) { const testCommands = note.commands.filter(c => c.command.includes('test') || c.command.includes('jest') || c.command.includes('vitest') ); if (testCommands.length > 0) { score += 3; reasons.push('Ran test commands'); } } if (note.tags?.some(t => t.includes('test'))) { score += 2; reasons.push('Tagged as testing'); } return { pattern: 'testing', score, reasons }; }

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