#!/usr/bin/env node /** * DSS Complexity Monitor Hook * Tracks code complexity metrics and warns on high-complexity code. * Written from scratch for DSS. */ const fs = require('fs'); const path = require('path'); // Configuration const DEFAULT_CONFIG = { complexity_monitor: { enabled: true, max_function_lines: 50, max_component_lines: 200, max_props: 10, max_nesting_depth: 4, warn_only: true } }; function loadConfig() { const configPath = path.join(process.env.HOME || '', '.dss', 'hooks-config.json'); try { if (fs.existsSync(configPath)) { const userConfig = JSON.parse(fs.readFileSync(configPath, 'utf8')); return { ...DEFAULT_CONFIG, ...userConfig }; } } catch (e) { // Use defaults } return DEFAULT_CONFIG; } function countLines(content) { return content.split('\n').length; } function countProps(content) { // Count props in interface/type definition const propsMatch = content.match(/(?:interface|type)\s+\w*Props[^{]*\{([^}]+)\}/); if (propsMatch) { const propsContent = propsMatch[1]; // Count semicolons or newlines with property definitions const props = propsContent.split(/[;\n]/).filter(line => { const trimmed = line.trim(); return trimmed && !trimmed.startsWith('//') && trimmed.includes(':'); }); return props.length; } return 0; } function countNestingDepth(content) { let maxDepth = 0; let currentDepth = 0; for (const char of content) { if (char === '{' || char === '(') { currentDepth++; maxDepth = Math.max(maxDepth, currentDepth); } else if (char === '}' || char === ')') { currentDepth = Math.max(0, currentDepth - 1); } } return maxDepth; } function countFunctions(content) { const patterns = [ /function\s+\w+\s*\([^)]*\)\s*\{/g, /const\s+\w+\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g, /const\s+\w+\s*=\s*(?:async\s*)?function/g ]; let count = 0; for (const pattern of patterns) { const matches = content.match(pattern); if (matches) count += matches.length; } return count; } function analyzeComplexity(content, filePath, config) { const issues = []; const monitorConfig = config.complexity_monitor || {}; const ext = path.extname(filePath).toLowerCase(); // Only analyze JS/TS files if (!['.js', '.jsx', '.ts', '.tsx'].includes(ext)) { return issues; } const lines = countLines(content); const props = countProps(content); const nesting = countNestingDepth(content); const functions = countFunctions(content); // Check component size (for tsx/jsx files) if (['.tsx', '.jsx'].includes(ext)) { if (lines > monitorConfig.max_component_lines) { issues.push({ type: 'component_size', severity: 'medium', message: `Component has ${lines} lines (max: ${monitorConfig.max_component_lines})`, suggestion: 'Consider breaking into smaller components' }); } if (props > monitorConfig.max_props) { issues.push({ type: 'prop_count', severity: 'medium', message: `Component has ${props} props (max: ${monitorConfig.max_props})`, suggestion: 'Consider grouping related props or using composition' }); } } // Check nesting depth if (nesting > monitorConfig.max_nesting_depth) { issues.push({ type: 'nesting_depth', severity: 'high', message: `Nesting depth of ${nesting} (max: ${monitorConfig.max_nesting_depth})`, suggestion: 'Extract nested logic into separate functions' }); } // Check function count (indicator of file doing too much) if (functions > 10) { issues.push({ type: 'function_count', severity: 'low', message: `File contains ${functions} functions`, suggestion: 'Consider splitting into multiple modules' }); } return issues; } function formatOutput(issues, filePath) { if (issues.length === 0) return ''; const severityIcons = { high: '[HIGH]', medium: '[MED]', low: '[LOW]' }; const lines = [`\n=== DSS Complexity Monitor: ${filePath} ===\n`]; for (const issue of issues) { const icon = severityIcons[issue.severity] || '[?]'; lines.push(`${icon} ${issue.message}`); lines.push(` Suggestion: ${issue.suggestion}\n`); } lines.push('='.repeat(50)); return lines.join('\n'); } async function main() { const config = loadConfig(); if (!config.complexity_monitor?.enabled) { process.exit(0); } // Read input from stdin let inputData; try { const chunks = []; for await (const chunk of process.stdin) { chunks.push(chunk); } inputData = JSON.parse(Buffer.concat(chunks).toString()); } catch (e) { process.exit(0); } const toolName = inputData.tool_name || ''; const toolInput = inputData.tool_input || {}; if (!['Edit', 'Write'].includes(toolName)) { process.exit(0); } const filePath = toolInput.file_path || ''; let content = ''; if (toolName === 'Write') { content = toolInput.content || ''; } else if (toolName === 'Edit') { content = toolInput.new_string || ''; } if (!content || !filePath) { process.exit(0); } const issues = analyzeComplexity(content, filePath, config); if (issues.length > 0) { const output = formatOutput(issues, filePath); console.error(output); } process.exit(0); } main().catch(() => process.exit(0));