Some checks failed
DSS Project Analysis / dss-context-update (push) Has been cancelled
This reverts commit 72cb7319f5.
216 lines
5.3 KiB
JavaScript
Executable File
216 lines
5.3 KiB
JavaScript
Executable File
#!/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));
|