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.
337 lines (336 loc) • 13.1 kB
JavaScript
/**
* Edge Case Detector Service
*
* Detects and categorizes skill execution failures for continuous improvement.
* Part of Task 1.5: MVP Edge Case Feedback Loop
*
* Features:
* - Automatic failure detection from skill executions
* - Error categorization (syntax, runtime, validation, timeout, dependency)
* - Severity calculation based on frequency and impact
* - Context capture (input, output, stack trace)
* - Integration with edge case deduplicator
*
* Usage:
* const detector = new EdgeCaseDetector(dbService, logger);
* const edgeCase = await detector.detectFailure(execution);
*/ import { createLogger } from '../lib/logging.js';
import { createError, ErrorCode, wrapError } from '../lib/errors.js';
import { generateShortCorrelationId } from '../lib/correlation.js';
import { EdgeCaseDeduplicator } from './edge-case-deduplicator.js';
/**
* Error categories for skill execution failures
*/ export var ErrorCategory = /*#__PURE__*/ function(ErrorCategory) {
ErrorCategory["SYNTAX"] = "syntax";
ErrorCategory["RUNTIME"] = "runtime";
ErrorCategory["VALIDATION"] = "validation";
ErrorCategory["TIMEOUT"] = "timeout";
ErrorCategory["DEPENDENCY"] = "dependency";
ErrorCategory["UNKNOWN"] = "unknown";
return ErrorCategory;
}({});
/**
* Severity levels for edge cases
*/ export var Severity = /*#__PURE__*/ function(Severity) {
Severity["LOW"] = "low";
Severity["MEDIUM"] = "medium";
Severity["HIGH"] = "high";
Severity["CRITICAL"] = "critical";
return Severity;
}({});
/**
* Edge case detector service
*/ export class EdgeCaseDetector {
dbService;
logger;
deduplicator;
config;
constructor(dbService, logger, config){
this.dbService = dbService;
this.logger = logger || createLogger('edge-case-detector');
this.deduplicator = new EdgeCaseDeduplicator(dbService, this.logger);
// Set default config
this.config = {
enableDeduplication: config?.enableDeduplication ?? true,
minSeverity: config?.minSeverity ?? "low",
customRules: config?.customRules ?? []
};
}
/**
* Detect failure from skill execution
*
* @param execution - Skill execution context
* @returns Edge case if failure detected, null otherwise
*/ async detectFailure(execution) {
try {
// 1. Check if execution failed
if (execution.success) {
this.logger.debug('Execution succeeded, no edge case detected', {
skill_id: execution.skill_id
});
return null;
}
if (!execution.error) {
this.logger.warn('Execution marked as failed but no error provided', {
skill_id: execution.skill_id
});
return null;
}
// 2. Extract error information
const error = execution.error;
const category = this.categorizeError(error);
const severity = this.calculateSeverity(category, execution);
// 3. Check minimum severity threshold
if (this.shouldIgnoreSeverity(severity)) {
this.logger.debug('Edge case below minimum severity threshold', {
skill_id: execution.skill_id,
severity,
minSeverity: this.config.minSeverity
});
return null;
}
// 4. Extract error details
const errorMessage = error instanceof Error ? error.message : String(error);
const stackTrace = error instanceof Error ? error.stack : undefined;
// 5. Capture context
const edgeCase = {
id: `edge-${generateShortCorrelationId()}`,
skill_id: execution.skill_id,
error_type: category,
severity,
error_message: errorMessage,
stack_trace: stackTrace,
input_context: JSON.stringify(execution.input),
output_context: execution.output,
first_seen: execution.timestamp,
last_seen: execution.timestamp,
occurrence_count: 1,
status: 'new',
metadata: {
agent_id: execution.agent_id,
task_id: execution.task_id,
duration_ms: execution.duration_ms,
timestamp: execution.timestamp.toISOString()
}
};
this.logger.info('Edge case detected', {
edge_case_id: edgeCase.id,
skill_id: edgeCase.skill_id,
error_type: category,
severity
});
// 6. Deduplicate if enabled
if (this.config.enableDeduplication) {
const isDuplicate = await this.deduplicator.deduplicateEdgeCase(edgeCase);
if (isDuplicate) {
this.logger.debug('Edge case is duplicate, incrementing occurrence count', {
edge_case_id: edgeCase.id
});
return null; // Already tracked
}
}
// 7. Store new edge case
await this.storeEdgeCase(edgeCase);
return edgeCase;
} catch (error) {
this.logger.error('Failed to detect edge case', error, {
skill_id: execution.skill_id
});
throw wrapError(error, 'EDGE_CASE_DETECTION_FAILED');
}
}
/**
* Categorize error into predefined categories
*
* @param error - Error to categorize
* @returns Error category
*/ categorizeError(error) {
const errorMessage = error.message.toLowerCase();
const errorName = error.name.toLowerCase();
// Check custom rules first
for (const rule of this.config.customRules){
if (rule.pattern.test(error.message)) {
this.logger.debug('Error matched custom rule', {
pattern: rule.pattern.toString(),
category: rule.category
});
return rule.category;
}
}
// Syntax errors
if (errorName.includes('syntax') || errorMessage.includes('syntax error') || errorMessage.includes('parse error') || errorMessage.includes('unexpected token')) {
return "syntax";
}
// Validation errors
if (errorName.includes('validation') || errorMessage.includes('invalid input') || errorMessage.includes('validation failed') || errorMessage.includes('required field') || errorMessage.includes('expected')) {
return "validation";
}
// Timeout errors
if (errorName.includes('timeout') || errorMessage.includes('timeout') || errorMessage.includes('timed out') || errorMessage.includes('deadline exceeded')) {
return "timeout";
}
// Dependency errors
if (errorMessage.includes('cannot find module') || errorMessage.includes('module not found') || errorMessage.includes('enoent') || errorMessage.includes('not found') || errorMessage.includes('missing dependency')) {
return "dependency";
}
// Runtime errors (default for most execution failures)
if (errorName.includes('error') || errorMessage.includes('runtime') || errorMessage.includes('execution failed')) {
return "runtime";
}
// Unknown
return "unknown";
}
/**
* Calculate severity based on error category and execution context
*
* @param category - Error category
* @param execution - Skill execution context
* @returns Severity level
*/ calculateSeverity(category, execution) {
// Base severity by category
let severity;
switch(category){
case "syntax":
severity = "high"; // Syntax errors block execution
break;
case "validation":
severity = "medium"; // Validation errors are fixable
break;
case "timeout":
severity = "medium"; // Timeouts may be transient
break;
case "dependency":
severity = "high"; // Missing dependencies block execution
break;
case "runtime":
severity = "medium"; // Runtime errors vary in impact
break;
case "unknown":
severity = "low"; // Unknown errors need investigation
break;
}
// Upgrade severity if error message indicates critical issue
if (execution.error) {
const message = execution.error.message.toLowerCase();
if (message.includes('critical') || message.includes('fatal') || message.includes('segmentation fault') || message.includes('out of memory')) {
severity = "critical";
}
}
return severity;
}
/**
* Check if severity should be ignored based on threshold
*/ shouldIgnoreSeverity(severity) {
const severityOrder = [
"low",
"medium",
"high",
"critical"
];
const currentIndex = severityOrder.indexOf(severity);
const minIndex = severityOrder.indexOf(this.config.minSeverity);
return currentIndex < minIndex;
}
/**
* Store edge case in database
*/ async storeEdgeCase(edgeCase) {
const sqlite = this.dbService.getAdapter('sqlite');
const result = await sqlite.insert('edge_cases', {
id: edgeCase.id,
skill_id: edgeCase.skill_id,
error_type: edgeCase.error_type,
severity: edgeCase.severity,
error_message: edgeCase.error_message,
stack_trace: edgeCase.stack_trace,
input_context: edgeCase.input_context,
output_context: edgeCase.output_context,
first_seen: edgeCase.first_seen.toISOString(),
last_seen: edgeCase.last_seen.toISOString(),
occurrence_count: edgeCase.occurrence_count,
status: edgeCase.status,
metadata: JSON.stringify(edgeCase.metadata)
});
if (!result.success) {
throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to store edge case', {
edge_case_id: edgeCase.id
}, result.error);
}
this.logger.info('Edge case stored successfully', {
edge_case_id: edgeCase.id
});
}
/**
* Get edge case by ID
*/ async getEdgeCase(id) {
const sqlite = this.dbService.getAdapter('sqlite');
const query = 'SELECT * FROM edge_cases WHERE id = ? LIMIT 1';
const rows = await sqlite.raw(query, [
id
]);
if (!Array.isArray(rows) || rows.length === 0) {
return null;
}
return this.mapRowToEdgeCase(rows[0]);
}
/**
* List all edge cases with optional filtering
*/ async listEdgeCases(filters) {
const sqlite = this.dbService.getAdapter('sqlite');
const queryFilters = [];
if (filters?.skill_id) {
queryFilters.push({
field: 'skill_id',
operator: 'eq',
value: filters.skill_id
});
}
if (filters?.error_type) {
queryFilters.push({
field: 'error_type',
operator: 'eq',
value: filters.error_type
});
}
if (filters?.severity) {
queryFilters.push({
field: 'severity',
operator: 'eq',
value: filters.severity
});
}
if (filters?.status) {
queryFilters.push({
field: 'status',
operator: 'eq',
value: filters.status
});
}
const rows = await sqlite.list('edge_cases', {
filters: queryFilters,
limit: filters?.limit || 100,
orderBy: 'last_seen',
order: 'desc'
});
return rows.map((row)=>this.mapRowToEdgeCase(row));
}
/**
* 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-detector.js.map