UNPKG

shipdeck

Version:

Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.

417 lines (341 loc) 12.3 kB
/** * Rule Classifier - Organizes 54 embedded rules into 3 tiers * * Tier 1 (Auto-fixable): 22 rules - formatting, imports, simple syntax * Tier 2 (Retry with AI): 20 rules - complexity, patterns, test coverage * Tier 3 (Human approval): 12 rules - architecture, security, breaking changes */ const { getAllRules } = require('../embedded-rules'); class RuleClassifier { constructor(config = {}) { this.config = config; this.rules = getAllRules(); // Tier classification mapping this.tierMapping = this._createTierMapping(); // Statistics this.stats = { totalRules: this.rules.length, tier1Count: 0, tier2Count: 0, tier3Count: 0 }; this._validateTierCounts(); } /** * Classify all violations in an artifact by tier */ async classifyViolations(artifact, context = {}) { const startTime = Date.now(); console.log(`📋 Classifying violations across ${this.rules.length} rules`); const violations = []; const tierCounts = { tier1: 0, tier2: 0, tier3: 0 }; // Check each rule against the artifact for (const rule of this.rules) { try { const ruleViolations = await this._checkRule(rule, artifact, context); if (ruleViolations.length > 0) { const tier = this.getTierForRule(rule); for (const violation of ruleViolations) { violations.push({ ...violation, rule: rule, tier: tier, severity: rule.severity, category: rule.category, autoFixable: tier === 1, requiresApproval: tier === 3 }); tierCounts[`tier${tier}`]++; } } } catch (error) { console.warn(`⚠️ Rule check failed for ${rule.key}: ${error.message}`); // Continue with other rules } } const processingTime = Date.now() - startTime; console.log(`📊 Classification complete: ${violations.length} violations (Tier 1: ${tierCounts.tier1}, Tier 2: ${tierCounts.tier2}, Tier 3: ${tierCounts.tier3}) in ${processingTime}ms`); return { violations, tierCounts, totalRules: this.rules.length, processingTime, summary: this._generateClassificationSummary(violations, tierCounts) }; } /** * Get tier assignment for a rule */ getTierForRule(rule) { const ruleKey = rule.key || `${rule.category}/${rule.name}`; return this.tierMapping[ruleKey] || this._inferTierFromRule(rule); } /** * Get all rules for a specific tier */ getRulesForTier(tier) { return this.rules.filter(rule => this.getTierForRule(rule) === tier); } /** * Get tier statistics */ getTierStatistics() { const tier1Rules = this.getRulesForTier(1); const tier2Rules = this.getRulesForTier(2); const tier3Rules = this.getRulesForTier(3); return { total: this.rules.length, tier1: { count: tier1Rules.length, categories: this._getCategoryCounts(tier1Rules), severities: this._getSeverityCounts(tier1Rules) }, tier2: { count: tier2Rules.length, categories: this._getCategoryCounts(tier2Rules), severities: this._getSeverityCounts(tier2Rules) }, tier3: { count: tier3Rules.length, categories: this._getCategoryCounts(tier3Rules), severities: this._getSeverityCounts(tier3Rules) } }; } // Private methods /** * Create comprehensive tier mapping for all 54 rules */ _createTierMapping() { return { // TIER 1 - AUTO-FIXABLE (22 rules) // TypeScript - Auto-fixable syntax and formatting 'typescript/avoid-any-type': 1, 'typescript/prefer-const': 1, 'typescript/no-unused-vars': 1, // React - Simple fixes 'react/no-async-client-components': 1, 'react/use-client-directive': 1, // Next.js - Configuration and simple patterns 'nextjs/server-components-default': 1, // Database - Basic query safety 'database/drizzle-type-safe-queries': 1, // Performance - Simple optimizations 'performance/image-optimization': 1, 'performance/lazy-loading': 1, // Testing - Basic test structure 'testing/mock-external-services': 1, // Development - Process rules 'development/no-build-commands': 1, 'development/hot-reload-only': 1, 'development/dev-server-usage': 1, 'development/source-maps': 1, 'development/error-overlay': 1, // Simple formatting and imports (6 additional rules) 'typescript/import-organization': 1, 'typescript/trailing-commas': 1, 'typescript/semicolons': 1, 'react/jsx-formatting': 1, 'react/prop-types-removal': 1, 'general/whitespace-cleanup': 1, // TIER 2 - RETRY WITH AI FEEDBACK (20 rules) // TypeScript - Complex type issues 'typescript/explicit-return-types': 2, 'typescript/strict-null-checks': 2, // React - Performance and patterns 'react/no-inline-styles-use-cn': 2, 'react/memoize-expensive': 2, 'react/error-boundaries': 2, // Next.js - App Router patterns 'nextjs/nextjs-15-async-params': 2, 'nextjs/app-router-patterns': 2, 'nextjs/dynamic-imports': 2, 'nextjs/metadata-api': 2, // Database - Performance and patterns 'database/connection-pooling': 2, 'database/transaction-handling': 2, // Performance - Complex optimizations 'performance/bundle-splitting': 2, 'performance/cache-strategies': 2, 'performance/lighthouse-targets': 2, // Testing - Coverage and patterns 'testing/test-after-code': 2, 'testing/edge-case-coverage': 2, 'testing/integration-tests': 2, 'testing/e2e-critical-paths': 2, // Security - Input validation (2 additional rules) 'security/input-validation': 2, 'security/rate-limiting': 2, // TIER 3 - HUMAN APPROVAL REQUIRED (12 rules) // Security - Critical vulnerabilities 'security/no-secrets-in-code': 3, 'security/auth-middleware': 3, 'security/csrf-protection': 3, // Database - Critical safety 'database/prepared-statements': 3, 'database/migration-safety': 3, // Architecture - Breaking changes (7 additional rules) 'architecture/breaking-api-changes': 3, 'architecture/database-schema-changes': 3, 'architecture/security-model-changes': 3, 'architecture/performance-regression': 3, 'architecture/dependency-updates': 3, 'architecture/configuration-changes': 3, 'architecture/deployment-strategy-changes': 3 }; } /** * Infer tier from rule characteristics if not explicitly mapped */ _inferTierFromRule(rule) { // Auto-fixable patterns if (rule.fix && typeof rule.fix === 'function') { return 1; } // Critical security or breaking change patterns if (rule.severity === 'critical' || rule.severity === 'security' && rule.description.toLowerCase().includes('critical')) { return 3; } // Human approval patterns if (rule.description.toLowerCase().includes('approval') || rule.description.toLowerCase().includes('breaking') || rule.description.toLowerCase().includes('migration')) { return 3; } // Default to Tier 2 (retry with AI) return 2; } /** * Check a single rule against the artifact */ async _checkRule(rule, artifact, context) { if (!rule.check || typeof rule.check !== 'function') { return []; } try { // Extract code content from artifact const code = this._extractCodeFromArtifact(artifact); if (!code) { return []; } // Run rule check const violations = rule.check(code, context); // Normalize violation format return Array.isArray(violations) ? violations : (violations ? [violations] : []); } catch (error) { throw new Error(`Rule check failed: ${error.message}`); } } /** * Extract code content from various artifact formats */ _extractCodeFromArtifact(artifact) { if (typeof artifact === 'string') { return artifact; } if (artifact.content) { return artifact.content; } if (artifact.files && Array.isArray(artifact.files)) { return artifact.files.map(file => file.content || '').join('\n'); } if (artifact.code) { return artifact.code; } return null; } /** * Generate classification summary */ _generateClassificationSummary(violations, tierCounts) { const severityCounts = {}; const categoryCounts = {}; violations.forEach(violation => { // Count by severity const severity = violation.severity || 'unknown'; severityCounts[severity] = (severityCounts[severity] || 0) + 1; // Count by category const category = violation.category || 'unknown'; categoryCounts[category] = (categoryCounts[category] || 0) + 1; }); return { totalViolations: violations.length, tierDistribution: { tier1: tierCounts.tier1, tier2: tierCounts.tier2, tier3: tierCounts.tier3 }, severityDistribution: severityCounts, categoryDistribution: categoryCounts, processingRecommendation: this._getProcessingRecommendation(tierCounts) }; } /** * Get processing recommendation based on tier distribution */ _getProcessingRecommendation(tierCounts) { const total = tierCounts.tier1 + tierCounts.tier2 + tierCounts.tier3; if (total === 0) { return 'No violations found - proceed with deployment'; } if (tierCounts.tier3 > 0) { return `Human approval required for ${tierCounts.tier3} critical issues`; } if (tierCounts.tier2 > 5) { return 'High complexity - recommend AI-assisted review'; } if (tierCounts.tier1 > 10) { return 'Many auto-fixable issues - run auto-fix first'; } return 'Standard processing - auto-fix and validate'; } /** * Get category counts for a set of rules */ _getCategoryCounts(rules) { const counts = {}; rules.forEach(rule => { const category = rule.category || 'unknown'; counts[category] = (counts[category] || 0) + 1; }); return counts; } /** * Get severity counts for a set of rules */ _getSeverityCounts(rules) { const counts = {}; rules.forEach(rule => { const severity = rule.severity || 'unknown'; counts[severity] = (counts[severity] || 0) + 1; }); return counts; } /** * Validate that we have the expected number of rules in each tier */ _validateTierCounts() { const stats = this.getTierStatistics(); console.log(`📊 Rule tier distribution: - Tier 1 (Auto-fix): ${stats.tier1.count} rules - Tier 2 (AI Retry): ${stats.tier2.count} rules - Tier 3 (Human Approval): ${stats.tier3.count} rules - Total: ${stats.total} rules`); // Warn if counts don't match expected const expected = { tier1: 22, tier2: 20, tier3: 12, total: 54 }; if (stats.total !== expected.total) { console.warn(`⚠️ Expected ${expected.total} total rules, found ${stats.total}`); } if (Math.abs(stats.tier1.count - expected.tier1) > 2) { console.warn(`⚠️ Tier 1 count significantly different from expected (${stats.tier1.count} vs ${expected.tier1})`); } if (Math.abs(stats.tier2.count - expected.tier2) > 2) { console.warn(`⚠️ Tier 2 count significantly different from expected (${stats.tier2.count} vs ${expected.tier2})`); } if (Math.abs(stats.tier3.count - expected.tier3) > 2) { console.warn(`⚠️ Tier 3 count significantly different from expected (${stats.tier3.count} vs ${expected.tier3})`); } } } module.exports = { RuleClassifier };