UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

538 lines (458 loc) 16.3 kB
/** * Pattern Analyzer for Workflow Codification System * * Analyzes workflow reflections to detect repeated patterns suitable for codification. * Implements similarity detection, metrics calculation, and pattern prioritization. * * @module pattern-analyzer */ import { PatternAnalyzerConfig, WorkflowReflection, WorkflowPattern, PatternAnalysisReport, AnalysisMetadata, WorkflowGroup, SecurityConstraints, ILogger, isValidPatternAnalyzerConfig, isWorkflowReflection, Priority, } from './types'; /** * Pattern Analyzer class for detecting workflow patterns from reflections */ export class PatternAnalyzer { private config: PatternAnalyzerConfig; private logger: ILogger; private securityConstraints: SecurityConstraints; constructor(config: PatternAnalyzerConfig, logger: ILogger) { if (!isValidPatternAnalyzerConfig(config)) { throw new Error('Invalid pattern analyzer configuration'); } this.config = config; this.logger = logger; // Security constraints (CWE-22 path traversal prevention) this.securityConstraints = { maxPathLength: 4096, maxFieldLength: 256, maxArraySize: 10000, maxFileSize: 100 * 1024 * 1024, // 100MB maxDbQueryLength: 10000, }; } /** * Generate normalized workflow signature from steps * Extracts and normalizes command sequences */ generateWorkflowSignature(steps: unknown[]): string { if (!Array.isArray(steps) || steps.length === 0) { return 'unknown'; } try { const normalized = steps .map((step) => { if (typeof step === 'object' && step !== null) { const stepObj = step as Record<string, unknown>; const keys = Object.keys(stepObj).slice(0, 3); return keys.join(' ').replace(/\s+/g, ' '); } return String(step).replace(/\s+/g, ' '); }) .filter((s) => s.length > 0); return normalized.length > 0 ? normalized.join(' → ') : 'unknown'; } catch { this.logger.warning('Failed to generate workflow signature'); return 'unknown'; } } /** * Calculate Jaccard similarity between two sets * Intersection / Union */ calculateJaccardSimilarity(stepsA: unknown[], stepsB: unknown[]): number { if (!Array.isArray(stepsA) || !Array.isArray(stepsB)) { return 0; } // Convert steps to comparable strings const setA = new Set(stepsA.map((s) => JSON.stringify(s))); const setB = new Set(stepsB.map((s) => JSON.stringify(s))); // Calculate intersection let intersection = 0; setA.forEach((item) => { if (setB.has(item)) { intersection++; } }); // Calculate union const union = new Set([...setA, ...setB]).size; if (union === 0) { return 0; } return Number((intersection / union).toFixed(3)); } /** * Calculate average pairwise similarity across reflection group */ calculateSimilarityScore(reflections: WorkflowReflection[]): number { if (reflections.length < 2) { return 1.0; } let totalSimilarity = 0; let comparisons = 0; // Calculate pairwise similarities for (let i = 0; i < reflections.length - 1; i++) { for (let j = i + 1; j < reflections.length; j++) { const refI = reflections[i]; const refJ = reflections[j]; if (!refI || !refJ) continue; const stepsA = refI.workflow_steps; const stepsB = refJ.workflow_steps; const similarity = this.calculateJaccardSimilarity(stepsA, stepsB); totalSimilarity += similarity; comparisons++; } } if (comparisons === 0) { return 0; } return Number((totalSimilarity / comparisons).toFixed(3)); } /** * Check if workflow is deterministic using heuristics * Detects non-deterministic patterns like randomness, timestamps, etc. */ checkDeterministic(reflections: WorkflowReflection[]): boolean { const nondeterministicPatterns = [ /random|timestamp|date|uuid|Math\.random|rand\(/gi, /api\..*\.com|http:\/\/|https:\/\//gi, /curl |wget |fetch\(/gi, ]; // Check for non-deterministic patterns in workflow steps for (const reflection of reflections) { const stepsString = JSON.stringify(reflection.workflow_steps); for (const pattern of nondeterministicPatterns) { if (pattern.test(stepsString)) { return false; } } } // Check output variance const uniqueOutputs = new Set(reflections.map((r) => r.output)); const uniqueCount = uniqueOutputs.size; const totalCount = reflections.length; // If more than 50% of outputs are unique, likely not deterministic // This allows small variations but detects high variance return uniqueCount <= Math.ceil(totalCount * 0.5); } /** * Estimate monthly cost savings from codifying workflow * Uses token costs and execution frequency */ estimateCostSavings(occurrenceCount: number, daysInWindow: number = 90): number { // Constants (using Z.ai pricing model) const aiInputTokens = 5000; const aiOutputTokens = 2000; const tokenCostPerMillion = 0.5; // $0.50 per 1M tokens const scriptCost = 0.0001; // Negligible // Calculate per-execution savings const totalTokens = aiInputTokens + aiOutputTokens; const aiCost = (totalTokens / 1000000) * tokenCostPerMillion; const savingsPerExecution = aiCost - scriptCost; // Estimate monthly executions const dailyRate = occurrenceCount / daysInWindow; const monthlyExecutions = Math.round(dailyRate * 30); // Calculate monthly savings const monthlySavings = monthlyExecutions * savingsPerExecution; return Number(monthlySavings.toFixed(2)); } /** * Calculate priority score based on multiple factors * Factors: occurrence count (40%), savings (30%), teams affected (20%), confidence (10%) */ calculatePriority( occurrenceCount: number, estimatedSavings: number, teamsCount: number, confidenceScore: number ): Priority { let score = 0; // Factor 1: Occurrence count (weight: 40%) if (occurrenceCount >= 20) { score += 40; } else if (occurrenceCount >= 10) { score += 25; } else { score += 10; } // Factor 2: Cost savings (weight: 30%) if (estimatedSavings >= 50) { score += 30; } else if (estimatedSavings >= 20) { score += 20; } else { score += 10; } // Factor 3: Teams affected (weight: 20%) if (teamsCount >= 3) { score += 20; } else if (teamsCount >= 2) { score += 12; } else { score += 5; } // Factor 4: Confidence score (weight: 10%) if (confidenceScore >= 0.9) { score += 10; } else if (confidenceScore >= 0.8) { score += 6; } else { score += 3; } // Determine priority if (score >= 75) { return 'high'; } else if (score >= 50) { return 'medium'; } else { return 'low'; } } /** * Group reflections by workflow signature */ private groupReflectionsBySignature( reflections: WorkflowReflection[] ): Map<string, WorkflowGroup> { const groups = new Map<string, WorkflowGroup>(); for (const reflection of reflections) { const signature = this.generateWorkflowSignature(reflection.workflow_steps); if (!groups.has(signature)) { groups.set(signature, { reflections: [], signature, }); } const group = groups.get(signature) as WorkflowGroup; group.reflections.push(reflection); } return groups; } /** * Filter groups by minimum occurrence threshold */ private filterByOccurrence( groups: Map<string, WorkflowGroup>, minOccurrences: number ): Map<string, WorkflowGroup> { const filtered = new Map<string, WorkflowGroup>(); groups.forEach((group, signature) => { if (group.reflections.length >= minOccurrences) { filtered.set(signature, group); } }); return filtered; } /** * Analyze candidate patterns and apply quality filters */ private analyzeCandidatePatterns( groups: Map<string, WorkflowGroup> ): WorkflowPattern[] { const patterns: WorkflowPattern[] = []; groups.forEach((group) => { const reflections = group.reflections; const occurrenceCount = reflections.length; // Calculate similarity score const similarityScore = this.calculateSimilarityScore(reflections); // Calculate average confidence const avgConfidence = reflections.reduce((sum, r) => sum + r.confidence, 0) / reflections.length; // Check if deterministic const isDeterministic = this.checkDeterministic(reflections); // Apply filters if ( similarityScore >= this.config.minSimilarity && avgConfidence >= this.config.minConfidence && isDeterministic ) { // Extract common workflow steps const firstReflection = reflections[0]; if (!firstReflection) return; const commonSteps = firstReflection.workflow_steps; // Extract unique teams const teamsSet = new Set(reflections.map((r) => r.team_id)); const teamsAffected = Array.from(teamsSet); const teamsCount = teamsAffected.length; // Estimate cost savings const estimatedSavings = this.estimateCostSavings( occurrenceCount, this.config.timeWindow ); // Calculate priority const priority = this.calculatePriority( occurrenceCount, estimatedSavings, teamsCount, avgConfidence ); // Create pattern object const pattern: WorkflowPattern = { pattern_name: group.signature, workflow_steps: commonSteps, occurrence_count: occurrenceCount, teams_affected: teamsAffected, similarity_score: similarityScore, confidence_score: Number(avgConfidence.toFixed(3)), deterministic: isDeterministic, estimated_savings_usd: estimatedSavings, priority, status: 'detected', }; patterns.push(pattern); this.logger.success( `Pattern detected: ${group.signature} (priority: ${priority}, savings: $${estimatedSavings}/month)` ); } }); return patterns; } /** * Sort patterns by priority and savings */ private sortPatterns(patterns: WorkflowPattern[]): WorkflowPattern[] { const priorityOrder: Record<Priority, number> = { high: 0, medium: 1, low: 2, }; return patterns.sort((a, b) => { const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority]; if (priorityDiff !== 0) { return priorityDiff; } return b.estimated_savings_usd - a.estimated_savings_usd; }); } /** * Generate metadata for the report */ private generateMetadata( totalReflections: number, patternsFound: number ): AnalysisMetadata { return { analysis_timestamp: new Date().toISOString(), time_window_days: this.config.timeWindow, total_reflections_analyzed: totalReflections, patterns_found: patternsFound, filters: { min_occurrences: this.config.minOccurrences, min_similarity: this.config.minSimilarity, min_confidence: this.config.minConfidence, }, }; } /** * Main analysis function * Orchestrates the complete pattern analysis workflow */ async analyzePatterns(reflections: WorkflowReflection[]): Promise<PatternAnalysisReport> { this.logger.log('Starting workflow pattern analysis'); this.logger.log( `Parameters: time_window=${this.config.timeWindow}d, ` + `min_occurrences=${this.config.minOccurrences}, ` + `min_similarity=${this.config.minSimilarity}, ` + `min_confidence=${this.config.minConfidence}` ); // Validate input if (!Array.isArray(reflections)) { throw new Error('Reflections must be an array'); } if (reflections.length > this.securityConstraints.maxArraySize) { throw new Error( `Reflections exceed maximum size: ${reflections.length} > ${this.securityConstraints.maxArraySize}` ); } // Validate all reflections const validReflections = reflections.filter((r) => { if (!isWorkflowReflection(r)) { this.logger.warning(`Invalid reflection structure: ${r}`); return false; } return true; }); this.logger.log(`Retrieved ${validReflections.length} valid reflections`); if (validReflections.length === 0) { this.logger.warning('No valid reflections found'); return { metadata: this.generateMetadata(0, 0), patterns: [], }; } // Group reflections by workflow signature this.logger.log('Grouping reflections by workflow similarity'); const allGroups = this.groupReflectionsBySignature(validReflections); this.logger.log(`Found ${allGroups.size} unique workflow signatures`); // Filter groups with minimum occurrences this.logger.log(`Filtering patterns with >= ${this.config.minOccurrences} occurrences`); const filteredGroups = this.filterByOccurrence(allGroups, this.config.minOccurrences); this.logger.log(`Found ${filteredGroups.size} groups after occurrence filter`); // Analyze and filter candidate patterns const candidatePatterns = this.analyzeCandidatePatterns(filteredGroups); this.logger.log(`Found ${candidatePatterns.length} candidate patterns after filtering`); // Sort patterns by priority const sortedPatterns = this.sortPatterns(candidatePatterns); // Generate report const report: PatternAnalysisReport = { metadata: this.generateMetadata(validReflections.length, sortedPatterns.length), patterns: sortedPatterns, }; return report; } /** * Format report as JSON */ formatAsJson(report: PatternAnalysisReport): string { return JSON.stringify(report, null, 2); } /** * Format report as summary */ formatAsSummary(report: PatternAnalysisReport): string { const { metadata, patterns } = report; const highPriority = patterns.filter((p) => p.priority === 'high').length; const mediumPriority = patterns.filter((p) => p.priority === 'medium').length; const lowPriority = patterns.filter((p) => p.priority === 'low').length; const topPatterns = patterns.slice(0, 5); const topPatternsStr = topPatterns .map( (p, i) => ` ${i + 1}. ${p.pattern_name} (priority: ${p.priority}, ` + `savings: $${p.estimated_savings_usd}/month)` ) .join('\n'); return ( 'Pattern Analysis Summary\n' + '========================\n' + '\n' + `Analysis Timestamp: ${metadata.analysis_timestamp}\n` + `Time Window: ${metadata.time_window_days} days\n` + `Total Reflections Analyzed: ${metadata.total_reflections_analyzed}\n` + `Patterns Found: ${metadata.patterns_found}\n` + '\n' + 'Filters:\n' + ` Min Occurrences: ${metadata.filters.min_occurrences}\n` + ` Min Similarity: ${metadata.filters.min_similarity}\n` + ` Min Confidence: ${metadata.filters.min_confidence}\n` + '\n' + 'Patterns by Priority:\n' + ` High: ${highPriority}\n` + ` Medium: ${mediumPriority}\n` + ` Low: ${lowPriority}\n` + '\n' + 'Top 5 Patterns:\n' + topPatternsStr ); } } export { PatternAnalysisReport, WorkflowPattern, WorkflowReflection, PatternAnalyzerConfig };