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
JavaScript
/**
* 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 };