Added Claude Skills
This commit is contained in:
@@ -0,0 +1,516 @@
|
||||
---
|
||||
name: custom-plugin-flutter-skill-animations
|
||||
description: Production-grade Flutter animations mastery - Implicit and explicit animations, AnimationController, Hero transitions, physics-based motion, Lottie/Rive integration, 60fps optimization with comprehensive code examples
|
||||
sasmp_version: "1.3.0"
|
||||
bonded_agent: 01-flutter-ui-development
|
||||
bond_type: PRIMARY_BOND
|
||||
---
|
||||
|
||||
# custom-plugin-flutter: Animations Skill
|
||||
|
||||
## Quick Start - Production Animation Pattern
|
||||
|
||||
```dart
|
||||
class AnimatedProductCard extends StatefulWidget {
|
||||
final Product product;
|
||||
final bool isSelected;
|
||||
|
||||
const AnimatedProductCard({
|
||||
required this.product,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
State<AnimatedProductCard> createState() => _AnimatedProductCardState();
|
||||
}
|
||||
|
||||
class _AnimatedProductCardState extends State<AnimatedProductCard>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _scaleAnimation;
|
||||
late final Animation<double> _opacityAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_scaleAnimation = Tween<double>(begin: 1.0, end: 0.95).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
||||
);
|
||||
|
||||
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
|
||||
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onTapDown(TapDownDetails details) => _controller.forward();
|
||||
void _onTapUp(TapUpDetails details) => _controller.reverse();
|
||||
void _onTapCancel() => _controller.reverse();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTapDown: _onTapDown,
|
||||
onTapUp: _onTapUp,
|
||||
onTapCancel: _onTapCancel,
|
||||
child: AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _scaleAnimation.value,
|
||||
child: Opacity(
|
||||
opacity: _opacityAnimation.value,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: widget.isSelected ? Colors.blue : Colors.grey,
|
||||
width: widget.isSelected ? 2 : 1,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: ProductContent(product: widget.product),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 1. Implicit Animations
|
||||
|
||||
### Built-in Animated Widgets
|
||||
|
||||
```dart
|
||||
// AnimatedContainer - Animate multiple properties
|
||||
AnimatedContainer(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
width: isExpanded ? 300 : 100,
|
||||
height: isExpanded ? 200 : 100,
|
||||
decoration: BoxDecoration(
|
||||
color: isActive ? Colors.blue : Colors.grey,
|
||||
borderRadius: BorderRadius.circular(isExpanded ? 16 : 8),
|
||||
boxShadow: isActive ? [BoxShadow(blurRadius: 10)] : [],
|
||||
),
|
||||
child: content,
|
||||
)
|
||||
|
||||
// AnimatedOpacity
|
||||
AnimatedOpacity(
|
||||
opacity: isVisible ? 1.0 : 0.0,
|
||||
duration: Duration(milliseconds: 200),
|
||||
child: content,
|
||||
)
|
||||
|
||||
// AnimatedPositioned (inside Stack)
|
||||
AnimatedPositioned(
|
||||
duration: Duration(milliseconds: 300),
|
||||
left: isLeft ? 0 : 100,
|
||||
top: isTop ? 0 : 100,
|
||||
child: widget,
|
||||
)
|
||||
|
||||
// AnimatedSwitcher - Cross-fade between widgets
|
||||
AnimatedSwitcher(
|
||||
duration: Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
currentText,
|
||||
key: ValueKey(currentText), // Important!
|
||||
),
|
||||
)
|
||||
|
||||
// TweenAnimationBuilder - Custom implicit animation
|
||||
TweenAnimationBuilder<double>(
|
||||
tween: Tween(begin: 0, end: progress),
|
||||
duration: Duration(milliseconds: 500),
|
||||
builder: (context, value, child) {
|
||||
return CircularProgressIndicator(value: value);
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## 2. Explicit Animations
|
||||
|
||||
### AnimationController Pattern
|
||||
|
||||
```dart
|
||||
class ExplicitAnimationWidget extends StatefulWidget {
|
||||
@override
|
||||
State<ExplicitAnimationWidget> createState() => _ExplicitAnimationWidgetState();
|
||||
}
|
||||
|
||||
class _ExplicitAnimationWidgetState extends State<ExplicitAnimationWidget>
|
||||
with TickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<Offset> _slideAnimation;
|
||||
late final Animation<double> _fadeAnimation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: Duration(milliseconds: 500),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
_slideAnimation = Tween<Offset>(
|
||||
begin: Offset(0, 1),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(0.0, 0.6, curve: Curves.easeOut),
|
||||
));
|
||||
|
||||
_fadeAnimation = Tween<double>(
|
||||
begin: 0,
|
||||
end: 1,
|
||||
).animate(CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(0.3, 1.0, curve: Curves.easeIn),
|
||||
));
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SlideTransition(
|
||||
position: _slideAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: _fadeAnimation,
|
||||
child: Content(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Animation Transitions
|
||||
|
||||
```dart
|
||||
// Built-in transitions
|
||||
FadeTransition(opacity: animation, child: widget)
|
||||
SlideTransition(position: offsetAnimation, child: widget)
|
||||
ScaleTransition(scale: animation, child: widget)
|
||||
RotationTransition(turns: animation, child: widget)
|
||||
SizeTransition(sizeFactor: animation, child: widget)
|
||||
|
||||
// AnimatedBuilder - Custom rendering
|
||||
AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
return Transform(
|
||||
transform: Matrix4.identity()
|
||||
..setEntry(3, 2, 0.001)
|
||||
..rotateY(_controller.value * pi),
|
||||
alignment: Alignment.center,
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Card(),
|
||||
)
|
||||
```
|
||||
|
||||
## 3. Hero Animations
|
||||
|
||||
```dart
|
||||
// Source screen
|
||||
Hero(
|
||||
tag: 'product-${product.id}',
|
||||
child: Image.network(product.imageUrl),
|
||||
)
|
||||
|
||||
// Destination screen
|
||||
Hero(
|
||||
tag: 'product-${product.id}',
|
||||
child: Image.network(product.imageUrl),
|
||||
)
|
||||
|
||||
// Custom Hero flight
|
||||
Hero(
|
||||
tag: 'avatar',
|
||||
flightShuttleBuilder: (
|
||||
flightContext,
|
||||
animation,
|
||||
flightDirection,
|
||||
fromHeroContext,
|
||||
toHeroContext,
|
||||
) {
|
||||
return AnimatedBuilder(
|
||||
animation: animation,
|
||||
builder: (context, _) {
|
||||
return CircleAvatar(
|
||||
radius: lerpDouble(40, 60, animation.value),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: CircleAvatar(),
|
||||
)
|
||||
```
|
||||
|
||||
## 4. Staggered Animations
|
||||
|
||||
```dart
|
||||
class StaggeredList extends StatefulWidget {
|
||||
@override
|
||||
State<StaggeredList> createState() => _StaggeredListState();
|
||||
}
|
||||
|
||||
class _StaggeredListState extends State<StaggeredList>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
final List<Animation<Offset>> _animations = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
duration: Duration(milliseconds: 1000),
|
||||
vsync: this,
|
||||
);
|
||||
|
||||
// Create staggered animations
|
||||
for (int i = 0; i < 5; i++) {
|
||||
final start = i * 0.1;
|
||||
final end = start + 0.4;
|
||||
_animations.add(
|
||||
Tween<Offset>(begin: Offset(1, 0), end: Offset.zero).animate(
|
||||
CurvedAnimation(
|
||||
parent: _controller,
|
||||
curve: Interval(start, end, curve: Curves.easeOut),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_controller.forward();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: List.generate(5, (index) {
|
||||
return SlideTransition(
|
||||
position: _animations[index],
|
||||
child: ListTile(title: Text('Item $index')),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 5. Physics-Based Animations
|
||||
|
||||
```dart
|
||||
// Spring simulation
|
||||
class SpringAnimation extends StatefulWidget {
|
||||
@override
|
||||
State<SpringAnimation> createState() => _SpringAnimationState();
|
||||
}
|
||||
|
||||
class _SpringAnimationState extends State<SpringAnimation>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late Animation<double> _animation;
|
||||
|
||||
final SpringDescription spring = SpringDescription(
|
||||
mass: 1,
|
||||
stiffness: 100,
|
||||
damping: 10,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(vsync: this);
|
||||
|
||||
final simulation = SpringSimulation(spring, 0, 1, 0);
|
||||
_controller.animateWith(simulation);
|
||||
|
||||
_animation = _controller.drive(Tween(begin: 0.0, end: 100.0));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedBuilder(
|
||||
animation: _animation,
|
||||
builder: (context, child) {
|
||||
return Transform.translate(
|
||||
offset: Offset(0, _animation.value),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: Ball(),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Lottie & Rive Integration
|
||||
|
||||
```dart
|
||||
// Lottie animation
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
Lottie.asset(
|
||||
'assets/loading.json',
|
||||
width: 200,
|
||||
height: 200,
|
||||
fit: BoxFit.contain,
|
||||
repeat: true,
|
||||
animate: true,
|
||||
onLoaded: (composition) {
|
||||
_controller.duration = composition.duration;
|
||||
},
|
||||
)
|
||||
|
||||
// Rive animation
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
RiveAnimation.asset(
|
||||
'assets/animation.riv',
|
||||
fit: BoxFit.cover,
|
||||
stateMachines: ['StateMachine1'],
|
||||
onInit: (artboard) {
|
||||
final controller = StateMachineController.fromArtboard(
|
||||
artboard,
|
||||
'StateMachine1',
|
||||
);
|
||||
artboard.addController(controller!);
|
||||
_trigger = controller.findInput<bool>('trigger') as SMITrigger;
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
## 7. Page Transitions
|
||||
|
||||
```dart
|
||||
// Custom page route
|
||||
class FadePageRoute<T> extends PageRouteBuilder<T> {
|
||||
final Widget page;
|
||||
|
||||
FadePageRoute({required this.page})
|
||||
: super(
|
||||
pageBuilder: (context, animation, secondaryAnimation) => page,
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
},
|
||||
transitionDuration: Duration(milliseconds: 300),
|
||||
);
|
||||
}
|
||||
|
||||
// Slide + fade combined
|
||||
transitionsBuilder: (context, animation, secondaryAnimation, child) {
|
||||
final offsetAnimation = Tween<Offset>(
|
||||
begin: Offset(1.0, 0.0),
|
||||
end: Offset.zero,
|
||||
).animate(CurvedAnimation(
|
||||
parent: animation,
|
||||
curve: Curves.easeInOut,
|
||||
));
|
||||
|
||||
return SlideTransition(
|
||||
position: offsetAnimation,
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Performance Optimization
|
||||
|
||||
```dart
|
||||
// Use const where possible
|
||||
const AnimatedContainer(...)
|
||||
|
||||
// RepaintBoundary for expensive animations
|
||||
RepaintBoundary(
|
||||
child: AnimatedWidget(),
|
||||
)
|
||||
|
||||
// AnimatedBuilder isolates rebuilds
|
||||
AnimatedBuilder(
|
||||
animation: _controller,
|
||||
child: ExpensiveWidget(), // Not rebuilt
|
||||
builder: (context, child) {
|
||||
return Transform.scale(
|
||||
scale: _controller.value,
|
||||
child: child, // Reused
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
// Avoid layout-triggering animations
|
||||
// Good: Transform, Opacity
|
||||
// Avoid: width/height in tight loops
|
||||
```
|
||||
|
||||
## Troubleshooting Guide
|
||||
|
||||
**Issue: Animation jank (dropped frames)**
|
||||
```
|
||||
1. Use RepaintBoundary to isolate
|
||||
2. Profile with DevTools Performance
|
||||
3. Avoid layout changes during animation
|
||||
4. Use AnimatedBuilder, not setState
|
||||
5. Check for heavy build methods
|
||||
```
|
||||
|
||||
**Issue: Animation doesn't start**
|
||||
```
|
||||
1. Verify AnimationController.forward() called
|
||||
2. Check vsync: this (TickerProviderStateMixin)
|
||||
3. Verify widget is mounted before animating
|
||||
4. Check duration is set
|
||||
```
|
||||
|
||||
**Issue: Animation leaks memory**
|
||||
```
|
||||
1. Dispose AnimationController in dispose()
|
||||
2. Cancel any listeners
|
||||
3. Use mounted check before setState
|
||||
```
|
||||
|
||||
## Animation Selection Guide
|
||||
|
||||
| Need | Solution |
|
||||
|------|----------|
|
||||
| Simple property change | AnimatedContainer |
|
||||
| Cross-fade widgets | AnimatedSwitcher |
|
||||
| Custom timing control | AnimationController |
|
||||
| Shared element | Hero |
|
||||
| List items appearing | Staggered animations |
|
||||
| Natural motion | Physics simulation |
|
||||
| Complex vector | Lottie/Rive |
|
||||
|
||||
---
|
||||
|
||||
**Create fluid, 60fps animations in Flutter.**
|
||||
@@ -0,0 +1,41 @@
|
||||
# animations Configuration
|
||||
# Category: general
|
||||
# Generated: 2025-12-30
|
||||
|
||||
skill:
|
||||
name: animations
|
||||
version: "1.0.0"
|
||||
category: general
|
||||
|
||||
settings:
|
||||
# Default settings for animations
|
||||
enabled: true
|
||||
log_level: info
|
||||
|
||||
# Category-specific defaults
|
||||
validation:
|
||||
strict_mode: false
|
||||
auto_fix: false
|
||||
|
||||
output:
|
||||
format: markdown
|
||||
include_examples: true
|
||||
|
||||
# Environment-specific overrides
|
||||
environments:
|
||||
development:
|
||||
log_level: debug
|
||||
validation:
|
||||
strict_mode: false
|
||||
|
||||
production:
|
||||
log_level: warn
|
||||
validation:
|
||||
strict_mode: true
|
||||
|
||||
# Integration settings
|
||||
integrations:
|
||||
# Enable/disable integrations
|
||||
git: true
|
||||
linter: true
|
||||
formatter: true
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "animations Configuration Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"skill": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string",
|
||||
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"api",
|
||||
"testing",
|
||||
"devops",
|
||||
"security",
|
||||
"database",
|
||||
"frontend",
|
||||
"algorithms",
|
||||
"machine-learning",
|
||||
"cloud",
|
||||
"containers",
|
||||
"general"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"version"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_level": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"debug",
|
||||
"info",
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"skill"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
# Animations Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide provides comprehensive documentation for the **animations** skill in the custom-plugin-flutter plugin.
|
||||
|
||||
## Category: General
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Familiarity with general concepts
|
||||
- Development environment set up
|
||||
- Plugin installed and configured
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
# Invoke the skill
|
||||
claude "animations - [your task description]"
|
||||
|
||||
# Example
|
||||
claude "animations - analyze the current implementation"
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Key Principles
|
||||
|
||||
1. **Consistency** - Follow established patterns
|
||||
2. **Clarity** - Write readable, maintainable code
|
||||
3. **Quality** - Validate before deployment
|
||||
|
||||
### Best Practices
|
||||
|
||||
- Always validate input data
|
||||
- Handle edge cases explicitly
|
||||
- Document your decisions
|
||||
- Write tests for critical paths
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Task 1: Basic Implementation
|
||||
|
||||
```python
|
||||
# Example implementation pattern
|
||||
def implement_animations(input_data):
|
||||
"""
|
||||
Implement animations functionality.
|
||||
|
||||
Args:
|
||||
input_data: Input to process
|
||||
|
||||
Returns:
|
||||
Processed result
|
||||
"""
|
||||
# Validate input
|
||||
if not input_data:
|
||||
raise ValueError("Input required")
|
||||
|
||||
# Process
|
||||
result = process(input_data)
|
||||
|
||||
# Return
|
||||
return result
|
||||
```
|
||||
|
||||
### Task 2: Advanced Usage
|
||||
|
||||
For advanced scenarios, consider:
|
||||
|
||||
- Configuration customization via `assets/config.yaml`
|
||||
- Validation using `scripts/validate.py`
|
||||
- Integration with other skills
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| Skill not found | Not installed | Run plugin sync |
|
||||
| Validation fails | Invalid config | Check config.yaml |
|
||||
| Unexpected output | Missing context | Provide more details |
|
||||
|
||||
## Related Resources
|
||||
|
||||
- SKILL.md - Skill specification
|
||||
- config.yaml - Configuration options
|
||||
- validate.py - Validation script
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-12-30*
|
||||
@@ -0,0 +1,87 @@
|
||||
# Animations Patterns
|
||||
|
||||
## Design Patterns
|
||||
|
||||
### Pattern 1: Input Validation
|
||||
|
||||
Always validate input before processing:
|
||||
|
||||
```python
|
||||
def validate_input(data):
|
||||
if data is None:
|
||||
raise ValueError("Data cannot be None")
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("Data must be a dictionary")
|
||||
return True
|
||||
```
|
||||
|
||||
### Pattern 2: Error Handling
|
||||
|
||||
Use consistent error handling:
|
||||
|
||||
```python
|
||||
try:
|
||||
result = risky_operation()
|
||||
except SpecificError as e:
|
||||
logger.error(f"Operation failed: {e}")
|
||||
handle_error(e)
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error")
|
||||
raise
|
||||
```
|
||||
|
||||
### Pattern 3: Configuration Loading
|
||||
|
||||
Load and validate configuration:
|
||||
|
||||
```python
|
||||
import yaml
|
||||
|
||||
def load_config(config_path):
|
||||
with open(config_path) as f:
|
||||
config = yaml.safe_load(f)
|
||||
validate_config(config)
|
||||
return config
|
||||
```
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### ❌ Don't: Swallow Exceptions
|
||||
|
||||
```python
|
||||
# BAD
|
||||
try:
|
||||
do_something()
|
||||
except:
|
||||
pass
|
||||
```
|
||||
|
||||
### ✅ Do: Handle Explicitly
|
||||
|
||||
```python
|
||||
# GOOD
|
||||
try:
|
||||
do_something()
|
||||
except SpecificError as e:
|
||||
logger.warning(f"Expected error: {e}")
|
||||
return default_value
|
||||
```
|
||||
|
||||
## Category-Specific Patterns: General
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
1. Start with the simplest implementation
|
||||
2. Add complexity only when needed
|
||||
3. Test each addition
|
||||
4. Document decisions
|
||||
|
||||
### Common Integration Points
|
||||
|
||||
- Configuration: `assets/config.yaml`
|
||||
- Validation: `scripts/validate.py`
|
||||
- Documentation: `references/GUIDE.md`
|
||||
|
||||
---
|
||||
|
||||
*Pattern library for animations skill*
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Validation script for animations skill.
|
||||
Category: general
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def validate_config(config_path: str) -> dict:
|
||||
"""
|
||||
Validate skill configuration file.
|
||||
|
||||
Args:
|
||||
config_path: Path to config.yaml
|
||||
|
||||
Returns:
|
||||
dict: Validation result with 'valid' and 'errors' keys
|
||||
"""
|
||||
errors = []
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
return {"valid": False, "errors": ["Config file not found"]}
|
||||
|
||||
try:
|
||||
with open(config_path, 'r') as f:
|
||||
config = yaml.safe_load(f)
|
||||
except yaml.YAMLError as e:
|
||||
return {"valid": False, "errors": [f"YAML parse error: {e}"]}
|
||||
|
||||
# Validate required fields
|
||||
if 'skill' not in config:
|
||||
errors.append("Missing 'skill' section")
|
||||
else:
|
||||
if 'name' not in config['skill']:
|
||||
errors.append("Missing skill.name")
|
||||
if 'version' not in config['skill']:
|
||||
errors.append("Missing skill.version")
|
||||
|
||||
# Validate settings
|
||||
if 'settings' in config:
|
||||
settings = config['settings']
|
||||
if 'log_level' in settings:
|
||||
valid_levels = ['debug', 'info', 'warn', 'error']
|
||||
if settings['log_level'] not in valid_levels:
|
||||
errors.append(f"Invalid log_level: {settings['log_level']}")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"config": config if not errors else None
|
||||
}
|
||||
|
||||
|
||||
def validate_skill_structure(skill_path: str) -> dict:
|
||||
"""
|
||||
Validate skill directory structure.
|
||||
|
||||
Args:
|
||||
skill_path: Path to skill directory
|
||||
|
||||
Returns:
|
||||
dict: Structure validation result
|
||||
"""
|
||||
required_dirs = ['assets', 'scripts', 'references']
|
||||
required_files = ['SKILL.md']
|
||||
|
||||
errors = []
|
||||
|
||||
# Check required files
|
||||
for file in required_files:
|
||||
if not os.path.exists(os.path.join(skill_path, file)):
|
||||
errors.append(f"Missing required file: {file}")
|
||||
|
||||
# Check required directories
|
||||
for dir in required_dirs:
|
||||
dir_path = os.path.join(skill_path, dir)
|
||||
if not os.path.isdir(dir_path):
|
||||
errors.append(f"Missing required directory: {dir}/")
|
||||
else:
|
||||
# Check for real content (not just .gitkeep)
|
||||
files = [f for f in os.listdir(dir_path) if f != '.gitkeep']
|
||||
if not files:
|
||||
errors.append(f"Directory {dir}/ has no real content")
|
||||
|
||||
return {
|
||||
"valid": len(errors) == 0,
|
||||
"errors": errors,
|
||||
"skill_name": os.path.basename(skill_path)
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Main validation entry point."""
|
||||
skill_path = Path(__file__).parent.parent
|
||||
|
||||
print(f"Validating animations skill...")
|
||||
print(f"Path: {skill_path}")
|
||||
|
||||
# Validate structure
|
||||
structure_result = validate_skill_structure(str(skill_path))
|
||||
print(f"\nStructure validation: {'PASS' if structure_result['valid'] else 'FAIL'}")
|
||||
if structure_result['errors']:
|
||||
for error in structure_result['errors']:
|
||||
print(f" - {error}")
|
||||
|
||||
# Validate config
|
||||
config_path = skill_path / 'assets' / 'config.yaml'
|
||||
if config_path.exists():
|
||||
config_result = validate_config(str(config_path))
|
||||
print(f"\nConfig validation: {'PASS' if config_result['valid'] else 'FAIL'}")
|
||||
if config_result['errors']:
|
||||
for error in config_result['errors']:
|
||||
print(f" - {error}")
|
||||
else:
|
||||
print("\nConfig validation: SKIPPED (no config.yaml)")
|
||||
|
||||
# Summary
|
||||
all_valid = structure_result['valid']
|
||||
print(f"\n==================================================")
|
||||
print(f"Overall: {'VALID' if all_valid else 'INVALID'}")
|
||||
|
||||
return 0 if all_valid else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user