claude-flow-novice
Version:
Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.
367 lines (366 loc) • 13.7 kB
JavaScript
/**
* Edge Case Analyzer Job
*
* Analyzes edge cases to detect patterns and generate dashboard data.
* Part of Task 1.5: MVP Edge Case Feedback Loop
*
* Features:
* - Pattern detection across multiple edge cases
* - Common error substring extraction
* - Common input pattern detection
* - Severity aggregation
* - Dashboard data generation
* - Scheduled analysis (cron job ready)
*
* Usage:
* const analyzer = new EdgeCaseAnalyzer(dbService, logger);
* const report = await analyzer.analyzeEdgeCases();
* const patterns = await analyzer.generatePatterns();
*/ import { createLogger } from '../lib/logging.js';
import { createError, ErrorCode } from '../lib/errors.js';
import { generateShortCorrelationId } from '../lib/correlation.js';
/**
* Edge case analyzer service
*/ export class EdgeCaseAnalyzer {
dbService;
logger;
config;
constructor(dbService, logger, config){
this.dbService = dbService;
this.logger = logger || createLogger('edge-case-analyzer');
// Set default config
this.config = {
minPatternOccurrences: config?.minPatternOccurrences ?? 3,
maxPatterns: config?.maxPatterns ?? 50,
minSubstringLength: config?.minSubstringLength ?? 10
};
}
/**
* Analyze all edge cases and generate comprehensive report
*
* @returns Analysis report
*/ async analyzeEdgeCases() {
this.logger.info('Starting edge case analysis');
try {
const sqlite = this.dbService.getAdapter('sqlite');
// Get total edge cases
const totalResult = await sqlite.raw('SELECT COUNT(*) as count FROM edge_cases');
const totalEdgeCases = Array.isArray(totalResult) && totalResult[0] ? totalResult[0].count : 0;
// Get new edge cases
const newResult = await sqlite.raw('SELECT COUNT(*) as count FROM edge_cases WHERE status = "new"');
const newEdgeCases = Array.isArray(newResult) && newResult[0] ? newResult[0].count : 0;
// Get top failures
const topFailures = await this.getTopFailures();
// Get high severity failures
const highSeverityFailures = await this.getHighSeverityFailures();
// Get failure trends
const trends = await this.getFailureTrends();
// Generate patterns
const patterns = await this.generatePatterns();
const patternsDetected = patterns.length;
const report = {
timestamp: new Date(),
totalEdgeCases,
newEdgeCases,
patternsDetected,
topFailures,
highSeverityFailures,
trends
};
this.logger.info('Edge case analysis complete', {
totalEdgeCases,
newEdgeCases,
patternsDetected
});
return report;
} catch (error) {
this.logger.error('Failed to analyze edge cases', error);
throw createError(ErrorCode.UNKNOWN_ERROR, 'Edge case analysis failed', undefined, error);
}
}
/**
* Generate failure patterns from edge cases
*
* @returns Array of detected patterns
*/ async generatePatterns() {
this.logger.info('Generating failure patterns');
try {
const sqlite = this.dbService.getAdapter('sqlite');
// 1. Query all edge cases
const edgeCases = await this.getAllNewEdgeCases();
if (edgeCases.length === 0) {
this.logger.debug('No edge cases to analyze');
return [];
}
// 2. Group by error type and skill
const groups = this.groupEdgeCases(edgeCases);
// 3. Find patterns
const patterns = [];
for (const [key, cases] of Object.entries(groups)){
if (cases.length < this.config.minPatternOccurrences) {
continue; // Need ≥minPatternOccurrences occurrences
}
const [skill_id, error_type] = key.split(':');
// Extract common patterns
const commonErrors = this.findCommonSubstrings(cases.map((c)=>c.error_message));
const commonInputs = this.findCommonInputPatterns(cases);
const severity = this.calculateAverageSeverity(cases);
const pattern = {
pattern_id: `pattern-${generateShortCorrelationId()}`,
skill_id,
error_type: error_type,
occurrence_count: cases.length,
common_errors: commonErrors,
common_inputs: commonInputs,
severity,
suggested_fix: null,
status: 'detected',
first_detected: new Date(),
last_updated: new Date()
};
patterns.push(pattern);
// Store pattern in database
await this.storePattern(pattern);
}
this.logger.info(`Generated ${patterns.length} patterns`);
return patterns.slice(0, this.config.maxPatterns);
} catch (error) {
this.logger.error('Failed to generate patterns', error);
throw createError(ErrorCode.UNKNOWN_ERROR, 'Pattern generation failed', undefined, error);
}
}
/**
* Get top failures by skill and error type
*/ async getTopFailures() {
const sqlite = this.dbService.getAdapter('sqlite');
const query = `
SELECT
skill_id,
error_type,
SUM(occurrence_count) as total_failures,
MAX(severity) as max_severity,
COUNT(*) as unique_cases,
MAX(last_seen) as most_recent
FROM edge_cases
WHERE status = 'new'
GROUP BY skill_id, error_type
ORDER BY total_failures DESC
LIMIT 10
`;
const rows = await sqlite.raw(query);
if (!Array.isArray(rows)) {
return [];
}
return rows.map((row)=>({
skill_id: row.skill_id,
error_type: row.error_type,
total_failures: row.total_failures,
max_severity: row.max_severity,
unique_cases: row.unique_cases,
most_recent: new Date(row.most_recent)
}));
}
/**
* Get high severity failures
*/ async getHighSeverityFailures() {
const sqlite = this.dbService.getAdapter('sqlite');
const query = `
SELECT
id,
skill_id,
error_type,
severity,
error_message,
occurrence_count,
last_seen
FROM edge_cases
WHERE severity IN ('critical', 'high')
AND status = 'new'
ORDER BY last_seen DESC, occurrence_count DESC
LIMIT 20
`;
const rows = await sqlite.raw(query);
if (!Array.isArray(rows)) {
return [];
}
return rows.map((row)=>({
id: row.id,
skill_id: row.skill_id,
error_type: row.error_type,
severity: row.severity,
error_message: row.error_message,
occurrence_count: row.occurrence_count,
last_seen: new Date(row.last_seen)
}));
}
/**
* Get failure trends (last 30 days)
*/ async getFailureTrends() {
const sqlite = this.dbService.getAdapter('sqlite');
const query = `
SELECT
DATE(first_seen) as date,
error_type,
COUNT(*) as new_failures,
SUM(occurrence_count) as total_occurrences
FROM edge_cases
WHERE first_seen >= datetime('now', '-30 days')
GROUP BY DATE(first_seen), error_type
ORDER BY date DESC
`;
const rows = await sqlite.raw(query);
if (!Array.isArray(rows)) {
return [];
}
return rows.map((row)=>({
date: row.date,
error_type: row.error_type,
new_failures: row.new_failures,
total_occurrences: row.total_occurrences
}));
}
/**
* Get all new edge cases
*/ async getAllNewEdgeCases() {
const sqlite = this.dbService.getAdapter('sqlite');
const query = 'SELECT * FROM edge_cases WHERE status = "new"';
const rows = await sqlite.raw(query);
if (!Array.isArray(rows)) {
return [];
}
return rows.map((row)=>this.mapRowToEdgeCase(row));
}
/**
* Group edge cases by skill and error type
*/ groupEdgeCases(edgeCases) {
const groups = {};
for (const edgeCase of edgeCases){
const key = `${edgeCase.skill_id}:${edgeCase.error_type}`;
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(edgeCase);
}
return groups;
}
/**
* Find common substrings in error messages
*/ findCommonSubstrings(messages) {
if (messages.length < 2) {
return messages;
}
const substrings = new Set();
// Find substrings in first message
const firstMessage = messages[0];
for(let i = 0; i < firstMessage.length; i++){
for(let j = i + this.config.minSubstringLength; j <= firstMessage.length; j++){
const substring = firstMessage.substring(i, j);
// Check if substring appears in all messages
if (messages.every((msg)=>msg.includes(substring))) {
substrings.add(substring);
}
}
}
// Return longest substrings (remove substrings that are part of longer ones)
const sorted = Array.from(substrings).sort((a, b)=>b.length - a.length);
const result = [];
for (const substring of sorted){
const isSubstringOfExisting = result.some((existing)=>existing.includes(substring));
if (!isSubstringOfExisting) {
result.push(substring);
}
}
return result.slice(0, 5); // Top 5 common substrings
}
/**
* Find common input patterns
*/ findCommonInputPatterns(edgeCases) {
const patterns = new Set();
// Extract input fields that appear in all cases
const inputObjects = edgeCases.map((ec)=>{
try {
return JSON.parse(ec.input_context);
} catch {
return {};
}
});
if (inputObjects.length === 0) {
return [];
}
// Find common keys
const firstKeys = Object.keys(inputObjects[0]);
for (const key of firstKeys){
if (inputObjects.every((obj)=>key in obj)) {
patterns.add(`common_field: ${key}`);
}
}
// Find common values
for (const key of firstKeys){
const values = inputObjects.map((obj)=>obj[key]).filter((v)=>v !== undefined);
const uniqueValues = new Set(values.map((v)=>JSON.stringify(v)));
if (uniqueValues.size === 1) {
patterns.add(`common_value: ${key}=${values[0]}`);
}
}
return Array.from(patterns).slice(0, 10); // Top 10 patterns
}
/**
* Calculate average severity
*/ calculateAverageSeverity(edgeCases) {
const severityOrder = [
'low',
'medium',
'high',
'critical'
];
const severityScores = edgeCases.map((ec)=>severityOrder.indexOf(ec.severity));
const avgScore = severityScores.reduce((a, b)=>a + b, 0) / severityScores.length;
const roundedScore = Math.round(avgScore);
return severityOrder[roundedScore];
}
/**
* Store pattern in database
*/ async storePattern(pattern) {
const sqlite = this.dbService.getAdapter('sqlite');
const result = await sqlite.insert('failure_patterns', {
id: pattern.pattern_id,
skill_id: pattern.skill_id,
error_type: pattern.error_type,
common_errors: JSON.stringify(pattern.common_errors),
common_inputs: JSON.stringify(pattern.common_inputs),
occurrence_count: pattern.occurrence_count,
severity: pattern.severity,
suggested_fix: pattern.suggested_fix,
status: pattern.status,
first_detected: pattern.first_detected.toISOString(),
last_updated: pattern.last_updated.toISOString(),
metadata: JSON.stringify({})
});
if (!result.success) {
this.logger.warn('Failed to store pattern', {
pattern_id: pattern.pattern_id,
error: result.error
});
}
}
/**
* Map database row to EdgeCase object
*/ mapRowToEdgeCase(row) {
return {
id: row.id,
skill_id: row.skill_id,
error_type: row.error_type,
severity: row.severity,
error_message: row.error_message,
stack_trace: row.stack_trace,
input_context: row.input_context,
output_context: row.output_context,
first_seen: new Date(row.first_seen),
last_seen: new Date(row.last_seen),
occurrence_count: row.occurrence_count,
status: row.status,
metadata: row.metadata ? JSON.parse(row.metadata) : {}
};
}
}
//# sourceMappingURL=edge-case-analyzer.js.map