UNPKG

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.

227 lines (226 loc) 8.98 kB
/** * Edge Case Deduplicator * * Handles deduplication of edge cases using SHA-256 signatures. * Normalizes context to detect similar errors with different IDs, timestamps, or numbers. */ import * as crypto from 'crypto'; export class EdgeCaseDeduplicator { /** * Generate SHA-256 signature for edge case deduplication * * Signature is based on: * - Type * - Category * - Normalized error message * - Normalized stack trace * * @param edgeCase Edge case input * @returns SHA-256 signature (64 hex characters) */ generateSignature(edgeCase) { const normalizedContext = this.normalizeContext(edgeCase.context); const signatureData = { type: edgeCase.type, category: edgeCase.category, context: normalizedContext }; const signatureString = JSON.stringify(signatureData); const hash = crypto.createHash('sha256'); hash.update(signatureString); return hash.digest('hex'); } /** * Normalize context for deduplication * * Replaces variable data with placeholders: * - Dates/timestamps → {date} * - IDs (hex, alphanumeric) → {id} * - Numbers → {number} * - File paths → normalized paths * - Line numbers in stack traces → {line} * * @param context Edge case context object * @returns Normalized JSON string */ normalizeContext(context) { const normalized = this.deepNormalize(context); return JSON.stringify(normalized, null, 0); } /** * Normalize stack trace for deduplication * * Removes: * - Line numbers * - Column numbers * - Absolute file paths (keep relative paths) * - Memory addresses * * @param stackTrace Stack trace string * @returns Normalized stack trace */ normalizeStackTrace(stackTrace) { let normalized = stackTrace; // Normalize line and column numbers: ":42:10" → ":{line}:{col}" normalized = normalized.replace(/:(\d+):(\d+)/g, ':{line}:{col}'); // Normalize single line numbers: ":42)" → ":{line})" normalized = normalized.replace(/:(\d+)\)/g, ':{line})'); // Normalize absolute paths to relative // /app/src/file.js → src/file.js // /home/user/project/file.js → file.js normalized = normalized.replace(/\/[a-zA-Z0-9_\-\/]+\//g, (match)=>{ // Keep only the last 2 path segments const segments = match.split('/').filter((s)=>s.length > 0); return segments.slice(-2).join('/') + '/'; }); // Normalize memory addresses: 0x7f1234567890 → {addr} normalized = normalized.replace(/0x[0-9a-f]+/gi, '{addr}'); return normalized; } /** * Deep normalize an object or string * * Recursively processes objects and arrays, * normalizing strings at leaf nodes. */ deepNormalize(value) { if (typeof value === 'string') { return this.normalizeString(value); } if (Array.isArray(value)) { return value.map((item)=>this.deepNormalize(item)); } if (typeof value === 'object' && value !== null) { const normalized = {}; // Special handling for error objects if (value.message !== undefined) { normalized.message = this.normalizeString(value.message); } if (value.stack !== undefined) { normalized.stack = this.normalizeStackTrace(value.stack); } // Normalize other properties for (const key of Object.keys(value)){ if (key !== 'message' && key !== 'stack') { normalized[key] = this.deepNormalize(value[key]); } } // Sort keys for consistent hashing const sorted = {}; Object.keys(normalized).sort().forEach((key)=>{ sorted[key] = normalized[key]; }); return sorted; } // Numbers, booleans, null stay as-is for non-string normalization // but we track them as {number} etc. in strings return value; } /** * Normalize a string by replacing variable data with placeholders */ normalizeString(str) { let normalized = str; // Normalize ISO 8601 dates/timestamps // 2025-11-16T13:00:00Z → {date} // 2025-11-16T13:00:00.123Z → {date} // 2025-11-16 13:00:00 → {date} normalized = normalized.replace(/\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z?/g, '{date}'); // Normalize standalone dates // 2025-11-16 → {date} normalized = normalized.replace(/\d{4}-\d{2}-\d{2}/g, '{date}'); // Normalize Unix timestamps (10 digits) // 1700000000 → {timestamp} normalized = normalized.replace(/\b\d{10}\b/g, '{timestamp}'); // Normalize UUIDs // 550e8400-e29b-41d4-a716-446655440000 → {uuid} normalized = normalized.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '{uuid}'); // Normalize hex IDs (8+ hex characters) // abc123def456 → {id} normalized = normalized.replace(/\b[a-f0-9]{8,}\b/gi, '{id}'); // Normalize task/agent IDs with prefixes // task-abc123, agent-xyz789, cfn-cli-1234567 → {id} normalized = normalized.replace(/(?:task|agent|cfn|job|run|session)-[a-z0-9\-]+/gi, '{id}'); // Normalize user/resource IDs in paths // /api/users/12345 → /api/users/{id} // /tasks/abc123/status → /tasks/{id}/status normalized = normalized.replace(/\/([a-z0-9\-]+)(?=\/|$)/gi, (match, id)=>{ // Only replace if it looks like an ID (contains numbers or is hex) if (/\d/.test(id) || /^[a-f0-9]+$/i.test(id)) { return '/{id}'; } return match; }); // Normalize standalone numbers // "Error 500", "timeout after 5000ms", "user 12345" → "Error {number}", etc. normalized = normalized.replace(/\b\d+\b/g, '{number}'); // Normalize memory addresses // 0x7f1234567890 → {addr} normalized = normalized.replace(/0x[0-9a-f]+/gi, '{addr}'); // Normalize file paths with line numbers // /app/file.js:42 → /app/file.js:{line} normalized = normalized.replace(/:(\d+)(?::(\d+))?/g, ':{line}'); return normalized; } /** * Check if two edge cases are similar based on their signatures * * @param signature1 First signature * @param signature2 Second signature * @returns True if signatures match */ areSimilar(signature1, signature2) { return signature1 === signature2; } /** * Calculate similarity score between two contexts (for fuzzy matching) * * This is for future enhancement - currently we use exact signature matching. * Could be used for "similar but not identical" edge case detection. * * @param context1 First context * @param context2 Second context * @returns Similarity score (0.0 to 1.0) */ calculateSimilarity(context1, context2) { const norm1 = this.normalizeContext(context1); const norm2 = this.normalizeContext(context2); // Simple Jaccard similarity on normalized strings const set1 = new Set(norm1.split(/\s+/)); const set2 = new Set(norm2.split(/\s+/)); const intersection = new Set(Array.from(set1).filter((x)=>set2.has(x))); const union = new Set([ ...Array.from(set1), ...Array.from(set2) ]); return intersection.size / union.size; } /** * Extract common pattern from multiple edge cases * * For future pattern detection feature. * * @param edgeCases Array of edge case inputs * @returns Common pattern descriptor */ extractPattern(edgeCases) { // Future implementation: extract common error patterns // For now, return basic statistics const types = new Map(); const categories = new Map(); for (const edgeCase of edgeCases){ types.set(edgeCase.type, (types.get(edgeCase.type) || 0) + 1); categories.set(edgeCase.category, (categories.get(edgeCase.category) || 0) + 1); } return { commonType: this.findMostCommon(types), commonCategory: this.findMostCommon(categories), totalCases: edgeCases.length }; } /** * Find most common value in a map */ findMostCommon(map) { let maxCount = 0; let maxKey = null; for (const [key, count] of Array.from(map.entries())){ if (count > maxCount) { maxCount = count; maxKey = key; } } return maxKey; } } //# sourceMappingURL=edge-case-deduplicator.js.map