Initial commit
This commit is contained in:
101
skills/fluxwing-component-creator/scripts/quick_validate.py
Normal file
101
skills/fluxwing-component-creator/scripts/quick_validate.py
Normal file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Quick validation wrapper for component creation workflow.
|
||||
Returns simple pass/fail + actionable error messages.
|
||||
|
||||
Usage:
|
||||
python quick_validate.py <component.uxm> <schema.json>
|
||||
|
||||
Returns:
|
||||
Exit 0 if valid, Exit 1 if errors found
|
||||
Human-friendly output for Claude to read
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from jsonschema import validate, ValidationError, Draft7Validator
|
||||
except ImportError:
|
||||
print("✗ Error: jsonschema library not found")
|
||||
print(" Install with: pip install jsonschema")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def quick_validate(uxm_file: str, schema_file: str) -> None:
|
||||
"""Validate and print result in Claude-friendly format."""
|
||||
try:
|
||||
# Load component
|
||||
with open(uxm_file) as f:
|
||||
component = json.load(f)
|
||||
|
||||
# Load schema
|
||||
with open(schema_file) as f:
|
||||
schema = json.load(f)
|
||||
|
||||
# Validate against schema
|
||||
validator = Draft7Validator(schema)
|
||||
errors = list(validator.iter_errors(component))
|
||||
|
||||
if errors:
|
||||
# Schema validation failed
|
||||
print(f"✗ Validation Failed: {uxm_file}")
|
||||
print()
|
||||
for i, error in enumerate(errors[:3], 1): # Show first 3 errors
|
||||
path = " → ".join(map(str, error.path)) if error.path else "root"
|
||||
print(f" Error {i}: {error.message}")
|
||||
print(f" Location: {path}")
|
||||
print()
|
||||
if len(errors) > 3:
|
||||
print(f" ... and {len(errors) - 3} more errors")
|
||||
sys.exit(1)
|
||||
|
||||
# Schema validation passed - check for .md file
|
||||
md_file = uxm_file.replace('.uxm', '.md')
|
||||
if not Path(md_file).exists():
|
||||
print(f"✗ Missing ASCII File: {md_file}")
|
||||
print(f" Component .uxm is valid, but .md template file not found")
|
||||
sys.exit(1)
|
||||
|
||||
# Success!
|
||||
comp_name = component['metadata']['name']
|
||||
comp_id = component['id']
|
||||
comp_type = component['type']
|
||||
state_count = len(component.get('behavior', {}).get('states', []))
|
||||
prop_count = len(component.get('props', {}))
|
||||
|
||||
print(f"✓ Valid: {comp_name} ({comp_id})")
|
||||
print(f" Type: {comp_type}")
|
||||
print(f" States: {state_count}")
|
||||
print(f" Props: {prop_count}")
|
||||
print(f" Files: {uxm_file} + {Path(md_file).name}")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"✗ Invalid JSON: {uxm_file}")
|
||||
print(f" Error: {e.msg} at line {e.lineno}, column {e.colno}")
|
||||
sys.exit(1)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"✗ File Not Found: {e.filename}")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Unexpected Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
print("Usage: quick_validate.py <component.uxm> <schema.json>")
|
||||
print()
|
||||
print("Quickly validates a uxscii component for use in workflows.")
|
||||
sys.exit(1)
|
||||
|
||||
quick_validate(sys.argv[1], sys.argv[2])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
207
skills/fluxwing-component-creator/scripts/validate_component.py
Normal file
207
skills/fluxwing-component-creator/scripts/validate_component.py
Normal file
@@ -0,0 +1,207 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fast, deterministic component validation using JSON Schema.
|
||||
Replaces the removed fluxwing-validator agent with reliable script.
|
||||
|
||||
Usage:
|
||||
python validate_component.py <component.uxm> <schema.json>
|
||||
|
||||
Returns:
|
||||
Exit 0 if valid, Exit 1 if errors found
|
||||
JSON output with validation results
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
from jsonschema import validate, ValidationError, Draft7Validator
|
||||
except ImportError:
|
||||
print("Error: jsonschema library not found. Install with: pip install jsonschema")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def validate_component(uxm_file_path: str, schema_path: str) -> dict:
|
||||
"""
|
||||
Validate a .uxm component file against the schema.
|
||||
|
||||
Returns:
|
||||
{
|
||||
"valid": bool,
|
||||
"errors": [list of error messages],
|
||||
"warnings": [list of warnings],
|
||||
"stats": {component stats}
|
||||
}
|
||||
"""
|
||||
# Load component
|
||||
try:
|
||||
with open(uxm_file_path) as f:
|
||||
component = json.load(f)
|
||||
except json.JSONDecodeError as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [{
|
||||
"path": [],
|
||||
"message": f"Invalid JSON: {e.msg} at line {e.lineno}",
|
||||
"type": "json_error"
|
||||
}],
|
||||
"warnings": [],
|
||||
"stats": {}
|
||||
}
|
||||
except FileNotFoundError:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [{
|
||||
"path": [],
|
||||
"message": f"Component file not found: {uxm_file_path}",
|
||||
"type": "file_not_found"
|
||||
}],
|
||||
"warnings": [],
|
||||
"stats": {}
|
||||
}
|
||||
|
||||
# Load schema
|
||||
try:
|
||||
with open(schema_path) as f:
|
||||
schema = json.load(f)
|
||||
except FileNotFoundError:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [{
|
||||
"path": [],
|
||||
"message": f"Schema file not found: {schema_path}",
|
||||
"type": "file_not_found"
|
||||
}],
|
||||
"warnings": [],
|
||||
"stats": {}
|
||||
}
|
||||
|
||||
# Validate against schema
|
||||
validator = Draft7Validator(schema)
|
||||
errors = []
|
||||
|
||||
for error in validator.iter_errors(component):
|
||||
errors.append({
|
||||
"path": list(error.path),
|
||||
"message": error.message,
|
||||
"type": "schema_violation"
|
||||
})
|
||||
|
||||
# Additional uxscii-specific checks
|
||||
warnings = []
|
||||
|
||||
# Check 1: ASCII file exists
|
||||
md_file = uxm_file_path.replace('.uxm', '.md')
|
||||
if not Path(md_file).exists():
|
||||
errors.append({
|
||||
"path": ["ascii", "templateFile"],
|
||||
"message": f"ASCII template file not found: {md_file}",
|
||||
"type": "missing_file"
|
||||
})
|
||||
else:
|
||||
# Check 2: Template variables match
|
||||
with open(md_file) as f:
|
||||
md_content = f.read()
|
||||
|
||||
# Extract {{variables}} from markdown
|
||||
md_vars = set(re.findall(r'\{\{(\w+)\}\}', md_content))
|
||||
|
||||
# Get variables from .uxm (handle both formats: array of strings or array of objects)
|
||||
ascii_vars = component.get('ascii', {}).get('variables', [])
|
||||
if ascii_vars and isinstance(ascii_vars[0], dict):
|
||||
# Array of objects format: [{"name": "text", "type": "string"}]
|
||||
uxm_vars = {v['name'] for v in ascii_vars if isinstance(v, dict) and 'name' in v}
|
||||
else:
|
||||
# Array of strings format: ["text", "value"]
|
||||
uxm_vars = set(ascii_vars)
|
||||
|
||||
missing = md_vars - uxm_vars
|
||||
if missing:
|
||||
warnings.append({
|
||||
"path": ["ascii", "variables"],
|
||||
"message": f"Variables in .md but not defined in .uxm: {', '.join(sorted(missing))}",
|
||||
"type": "variable_mismatch"
|
||||
})
|
||||
|
||||
# Check 3: Accessibility requirements
|
||||
if component.get('behavior', {}).get('interactive'):
|
||||
accessibility = component.get('accessibility', {})
|
||||
if not accessibility.get('role'):
|
||||
warnings.append({
|
||||
"path": ["accessibility", "role"],
|
||||
"message": "Interactive component should have ARIA role",
|
||||
"type": "accessibility"
|
||||
})
|
||||
if not accessibility.get('focusable'):
|
||||
warnings.append({
|
||||
"path": ["accessibility", "focusable"],
|
||||
"message": "Interactive component should be focusable",
|
||||
"type": "accessibility"
|
||||
})
|
||||
|
||||
# Check 4: ASCII dimensions
|
||||
ascii_config = component.get('ascii', {})
|
||||
width = ascii_config.get('width', 0)
|
||||
height = ascii_config.get('height', 0)
|
||||
|
||||
if width > 120:
|
||||
warnings.append({
|
||||
"path": ["ascii", "width"],
|
||||
"message": f"Width {width} exceeds recommended max of 120",
|
||||
"type": "dimensions"
|
||||
})
|
||||
|
||||
if height > 50:
|
||||
warnings.append({
|
||||
"path": ["ascii", "height"],
|
||||
"message": f"Height {height} exceeds recommended max of 50",
|
||||
"type": "dimensions"
|
||||
})
|
||||
|
||||
# Check 5: States should have properties
|
||||
states = component.get('behavior', {}).get('states', [])
|
||||
for state in states:
|
||||
if 'properties' not in state:
|
||||
warnings.append({
|
||||
"path": ["behavior", "states", state.get('name', 'unknown')],
|
||||
"message": f"State '{state.get('name')}' has no properties defined",
|
||||
"type": "incomplete_state"
|
||||
})
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"warnings": warnings,
|
||||
"stats": {
|
||||
"id": component.get('id'),
|
||||
"type": component.get('type'),
|
||||
"version": component.get('version'),
|
||||
"states": len(component.get('behavior', {}).get('states', [])),
|
||||
"props": len(component.get('props', {})),
|
||||
"interactive": component.get('behavior', {}).get('interactive', False),
|
||||
"has_accessibility": bool(component.get('accessibility'))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: validate_component.py <component.uxm> <schema.json>")
|
||||
print()
|
||||
print("Validates a uxscii component against the JSON schema.")
|
||||
print("Returns JSON with validation results and exits 0 if valid, 1 if invalid.")
|
||||
sys.exit(1)
|
||||
|
||||
uxm_file = sys.argv[1]
|
||||
schema_file = sys.argv[2]
|
||||
|
||||
result = validate_component(uxm_file, schema_file)
|
||||
print(json.dumps(result, indent=2))
|
||||
|
||||
sys.exit(0 if result["valid"] else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user