Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
215
dss-claude-plugin/hooks/scripts/complexity-monitor.js
Executable file
215
dss-claude-plugin/hooks/scripts/complexity-monitor.js
Executable file
@@ -0,0 +1,215 @@
|
||||
#!/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));
|
||||
Reference in New Issue
Block a user