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:
Digital Production Factory
2025-12-09 18:45:48 -03:00
commit 276ed71f31
884 changed files with 373737 additions and 0 deletions

View File

@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""
DSS Component Checker Hook
Validates React components for best practices and accessibility.
Written from scratch for DSS.
"""
import json
import re
import sys
from pathlib import Path
# React component patterns to check
COMPONENT_PATTERNS = [
# Accessibility checks
{
"id": "a11y-img-alt",
"regex": r"<img\s+(?![^>]*alt=)[^>]*>",
"category": "accessibility",
"severity": "high",
"message": "Missing alt attribute on <img>. Add alt text for accessibility.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "a11y-button-type",
"regex": r"<button\s+(?![^>]*type=)[^>]*>",
"category": "accessibility",
"severity": "medium",
"message": "Button missing type attribute. Add type='button' or type='submit'.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "a11y-anchor-href",
"regex": r"<a\s+(?![^>]*href=)[^>]*>",
"category": "accessibility",
"severity": "high",
"message": "Anchor tag missing href. Use button for actions without navigation.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "a11y-click-handler",
"regex": r"<(?:div|span)\s+[^>]*onClick",
"category": "accessibility",
"severity": "medium",
"message": "Click handler on non-interactive element. Use <button> or add role/tabIndex.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "a11y-form-label",
"regex": r"<input\s+(?![^>]*(?:aria-label|id))[^>]*>",
"category": "accessibility",
"severity": "medium",
"message": "Input may be missing label association. Add id with <label> or aria-label.",
"file_types": [".jsx", ".tsx"]
},
# React best practices
{
"id": "react-key-index",
"regex": r"\.map\([^)]*,\s*(?:index|i|idx)\s*\)[^{]*key=\{(?:index|i|idx)\}",
"category": "react",
"severity": "medium",
"message": "Using array index as key. Use unique, stable IDs when possible.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "react-bind-render",
"regex": r"onClick=\{[^}]*\.bind\(this",
"category": "react",
"severity": "low",
"message": "Binding in render creates new function each time. Use arrow function or bind in constructor.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "react-inline-style-object",
"regex": r"style=\{\{[^}]{100,}\}\}",
"category": "react",
"severity": "low",
"message": "Large inline style object. Consider extracting to a constant or CSS module.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "react-console-log",
"regex": r"console\.(log|debug|info)\(",
"category": "react",
"severity": "low",
"message": "Console statement detected. Remove before production.",
"file_types": [".js", ".jsx", ".ts", ".tsx"]
},
# TypeScript checks
{
"id": "ts-any-type",
"regex": r":\s*any\b",
"category": "typescript",
"severity": "medium",
"message": "Using 'any' type loses type safety. Consider using a specific type or 'unknown'.",
"file_types": [".ts", ".tsx"]
},
{
"id": "ts-type-assertion",
"regex": r"as\s+any\b",
"category": "typescript",
"severity": "medium",
"message": "Type assertion to 'any'. This bypasses type checking.",
"file_types": [".ts", ".tsx"]
},
# Component structure
{
"id": "component-no-export",
"regex": r"^(?!.*export).*(?:function|const)\s+[A-Z][a-zA-Z]*\s*(?:=|:|\()",
"category": "structure",
"severity": "low",
"message": "Component may not be exported. Ensure it's exported if meant to be reused.",
"file_types": [".jsx", ".tsx"]
},
{
"id": "component-missing-displayname",
"regex": r"(?:forwardRef|memo)\s*\([^)]*\)",
"category": "structure",
"severity": "low",
"message": "HOC component may need displayName for debugging.",
"file_types": [".jsx", ".tsx"]
}
]
def get_config():
"""Load hook configuration."""
config_path = Path.home() / ".dss" / "hooks-config.json"
default_config = {
"component_checker": {
"enabled": True,
"categories": ["accessibility", "react", "typescript"],
"min_severity": "low"
}
}
if config_path.exists():
try:
with open(config_path) as f:
user_config = json.load(f)
return {**default_config, **user_config}
except:
pass
return default_config
def severity_level(severity: str) -> int:
"""Convert severity to numeric level."""
levels = {"low": 1, "medium": 2, "high": 3}
return levels.get(severity, 0)
def check_content(content: str, file_path: str, config: dict) -> list:
"""Check content for component issues."""
issues = []
file_ext = Path(file_path).suffix.lower()
checker_config = config.get("component_checker", {})
enabled_categories = checker_config.get("categories", [])
min_severity = checker_config.get("min_severity", "low")
min_level = severity_level(min_severity)
for pattern_def in COMPONENT_PATTERNS:
# Skip if file type doesn't match
if file_ext not in pattern_def.get("file_types", []):
continue
# Skip if category not enabled
if enabled_categories and pattern_def["category"] not in enabled_categories:
continue
# Skip if below minimum severity
if severity_level(pattern_def["severity"]) < min_level:
continue
if re.search(pattern_def["regex"], content, re.MULTILINE):
issues.append({
"id": pattern_def["id"],
"category": pattern_def["category"],
"severity": pattern_def["severity"],
"message": pattern_def["message"]
})
return issues
def format_output(issues: list, file_path: str) -> str:
"""Format issues for display."""
if not issues:
return ""
severity_icons = {
"high": "[HIGH]",
"medium": "[MED]",
"low": "[LOW]"
}
category_labels = {
"accessibility": "A11Y",
"react": "REACT",
"typescript": "TS",
"structure": "STRUCT"
}
lines = [f"\n=== DSS Component Checker: {file_path} ===\n"]
# Group by category
by_category = {}
for issue in issues:
cat = issue["category"]
if cat not in by_category:
by_category[cat] = []
by_category[cat].append(issue)
for category, cat_issues in by_category.items():
label = category_labels.get(category, category.upper())
lines.append(f"[{label}]")
for issue in cat_issues:
sev = severity_icons.get(issue["severity"], "[?]")
lines.append(f" {sev} {issue['message']}")
lines.append("")
lines.append("=" * 50)
return "\n".join(lines)
def main():
"""Main hook entry point."""
config = get_config()
if not config.get("component_checker", {}).get("enabled", True):
sys.exit(0)
# Read hook input from stdin
try:
input_data = json.loads(sys.stdin.read())
except json.JSONDecodeError:
sys.exit(0)
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
if tool_name not in ["Edit", "Write"]:
sys.exit(0)
file_path = tool_input.get("file_path", "")
file_ext = Path(file_path).suffix.lower() if file_path else ""
# Only check React/TypeScript files
if file_ext not in [".jsx", ".tsx", ".js", ".ts"]:
sys.exit(0)
# Get content to check
if tool_name == "Write":
content = tool_input.get("content", "")
elif tool_name == "Edit":
content = tool_input.get("new_string", "")
else:
content = ""
if not content:
sys.exit(0)
issues = check_content(content, file_path, config)
if issues:
output = format_output(issues, file_path)
print(output, file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()