UNPKG

@invisiblecities/sidequest-cqo

Version:

Configuration-agnostic TypeScript and ESLint orchestrator with real-time watch mode, SQLite persistence, and intelligent terminal detection

194 lines 7.14 kB
/** * Database utility functions for Code Quality Orchestrator * Includes hashing, deduplication, and data transformation helpers */ import { createHash } from "node:crypto"; // ============================================================================ // Hash Generation for Deduplication // ============================================================================ /** * Generate a consistent hash for violation deduplication * Hash includes: file_path + rule_id + message (excludes line_number for stability) * This makes violations more stable across code edits that shift line numbers */ export function generateViolationHash(violation) { // Normalize the message to make it more stable const messageStr = typeof violation.message === "string" ? violation.message : String(violation.message || ""); const normalizedMessage = messageStr .replaceAll(/line \d+/g, "line X") // Replace specific line numbers in messages .replaceAll(/\d+:\d+/g, "X:Y") // Replace line:column references .trim(); const hashInput = [ violation.file_path, // Intentionally exclude line_number for logical stability across edits violation.rule_id || "unknown", normalizedMessage, ].join("|"); return createHash("sha256").update(hashInput).digest("hex"); } /** * Convert orchestrator violation to database violation format */ export function violationToDatabaseFormat(violation) { const hash = generateViolationHash({ file_path: violation.file, line_number: violation.line, rule_id: violation.rule || violation.code || "unknown", message: violation.message || "No message provided", }); return { file_path: violation.file, rule_id: violation.rule || violation.code || "unknown", category: violation.category, severity: violation.severity, source: violation.source, message: violation.message || "No message provided", line_number: violation.line || null, // eslint-disable-line unicorn/no-null column_number: violation.column || null, // eslint-disable-line unicorn/no-null code_snippet: violation.code || null, // eslint-disable-line unicorn/no-null hash, // first_seen_at and last_seen_at will use DEFAULT CURRENT_TIMESTAMP // status will use DEFAULT 'active' }; } /** * Convert multiple violations to database format with batch processing */ export function violationsToDatabaseFormat(violations) { return violations.map((violation) => violationToDatabaseFormat(violation)); } // ============================================================================ // Delta Computation for Historical Tracking // ============================================================================ /** * Compare two sets of violation hashes to compute deltas */ export function computeViolationDeltas(previousHashes, currentHashes) { const previousSet = new Set(previousHashes); const currentSet = new Set(currentHashes); const deltas = []; // Find added violations for (const hash of currentHashes) { if (!previousSet.has(hash)) { deltas.push({ violation_hash: hash, action: "added", }); } } // Find removed violations for (const hash of previousHashes) { if (!currentSet.has(hash)) { deltas.push({ violation_hash: hash, action: "removed", }); } } // Find unchanged violations (useful for analytics) for (const hash of currentHashes) { if (previousSet.has(hash)) { deltas.push({ violation_hash: hash, action: "unchanged", }); } } return deltas; } /** * Batch process deltas for database insertion */ export function prepareDeltasForInsertion(checkId, deltas) { return deltas.map((delta) => ({ check_id: checkId, violation_hash: delta.violation_hash, action: delta.action, previous_line: delta.previous_line || null, // eslint-disable-line unicorn/no-null previous_message: delta.previous_message || null, // eslint-disable-line unicorn/no-null })); } // ============================================================================ // Data Formatting and Transformation // ============================================================================ /** * Format datetime for SQLite storage */ export function formatDateTimeForDatabase(date = new Date()) { return date.toISOString(); } /** * Batch array into chunks for efficient database operations */ export function chunk(array, size) { const chunks = []; for (let index = 0; index < array.length; index += size) { chunks.push(array.slice(index, index + size)); } return chunks; } // ============================================================================ // Query Helpers // ============================================================================ // ============================================================================ // Performance Monitoring Helpers // ============================================================================ /** * Create performance metric entry */ export function createPerformanceMetric(type, value, unit, context) { return { metric_type: type, metric_value: value, metric_unit: unit, context: context || null, // eslint-disable-line unicorn/no-null recorded_at: formatDateTimeForDatabase(), }; } // ============================================================================ // Validation Helpers // ============================================================================ /** * Validate violation data before database insertion */ export function validateViolation(violation) { const errors = []; if (!violation.file_path?.trim()) { errors.push("file_path is required"); } if (!violation.rule_id?.trim()) { errors.push("rule_id is required"); } if (!violation.category?.trim()) { errors.push("category is required"); } if (!["error", "warn", "info"].includes(violation.severity)) { errors.push("severity must be error, warn, or info"); } if (!["typescript", "eslint", "unused-exports", "zod-detection"].includes(violation.source)) { errors.push("source must be typescript, eslint, unused-exports, or zod-detection"); } if (!violation.message?.trim()) { errors.push("message is required"); } if (!violation.hash?.trim()) { errors.push("hash is required"); } return errors; } /** * Validate and clean violation data */ export function sanitizeViolation(violation) { return { ...violation, file_path: violation.file_path.trim(), rule_id: violation.rule_id.trim(), category: violation.category.trim(), message: violation.message.trim(), code_snippet: violation.code_snippet?.trim() || null, }; } //# sourceMappingURL=utils.js.map