commit 7941a65c00438480a80f3c48f5d9fbc131ae02c3 Author: Zhongwei Li Date: Sat Nov 29 18:50:26 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..93ffa8b --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "fullstack-dev-skills", + "description": "Comprehensive skill pack for full-stack developers covering frameworks, workflows, and security. Includes 19 specialized skills: NestJS, Django, FastAPI, React, React Native, Flutter experts, plus debugging, monitoring, architecture design, code review, and security analysis.", + "version": "0.0.4", + "author": { + "name": "jeffallan", + "email": "github@jeffallan" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..d3bf997 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# fullstack-dev-skills + +Comprehensive skill pack for full-stack developers covering frameworks, workflows, and security. Includes 19 specialized skills: NestJS, Django, FastAPI, React, React Native, Flutter experts, plus debugging, monitoring, architecture design, code review, and security analysis. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..37ffda3 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,117 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:Jeffallan/claude-skills:", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "4923855298b5ad06f6784d3cd1adc921a4531430", + "treeHash": "c029de06f55bc5f8e3278d56982c6f2cc08605f8a17ea60acbecb2889076c4af", + "generatedAt": "2025-11-28T10:11:47.407936Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "fullstack-dev-skills", + "description": "Comprehensive skill pack for full-stack developers covering frameworks, workflows, and security. Includes 19 specialized skills: NestJS, Django, FastAPI, React, React Native, Flutter experts, plus debugging, monitoring, architecture design, code review, and security analysis.", + "version": "0.0.4" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "e9d534d2d5fbf4d7f32ae697025dbbbe965c9f78a9c8623c3f828bfe9a5b7e43" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "88183794d930b2f7f8e5c19a229c346bf3e6053c99b41b351d0c780b88a293f7" + }, + { + "path": "skills/fullstack-guardian/SKILL.md", + "sha256": "78e520be638fcb35437b52fcd76ca6fdacd4ae3be8d1cf07eaa7b99754a0545a" + }, + { + "path": "skills/code-documenter/SKILL.md", + "sha256": "263868ec938fffb822f2b28a8a18e5d5a077cdfbd28e806524d517ed05b80f30" + }, + { + "path": "skills/spec-miner/SKILL.md", + "sha256": "634b42dc1b5830fbccc247e4131abc5e97cb60ce945ecb1dbb327d5f389faf1f" + }, + { + "path": "skills/fastapi-expert/SKILL.md", + "sha256": "66f70b6406b68e6b6adf89a66cefb6ac9e4c1e389db0c42e2791a3a9140aaba0" + }, + { + "path": "skills/monitoring-expert/SKILL.md", + "sha256": "33fc9b67c6052691ef7697dedef4e225bf0d8136b74fad54e2e71a0f3d05cc16" + }, + { + "path": "skills/devops-engineer/SKILL.md", + "sha256": "0874d7ceb2f4e1bca8b57425880c00d4574edba07f3acb5364f7268a9667da46" + }, + { + "path": "skills/debugging-wizard/SKILL.md", + "sha256": "5b4201f5ba509ea4f362a209a5b1f953bc9558eed6387311c0c9e7b21a6d85c8" + }, + { + "path": "skills/test-master/SKILL.md", + "sha256": "15c8849e43a5625c4c517e77d249b28c96d0ca82f04c031e33dfcc19a7e35b3a" + }, + { + "path": "skills/django-expert/SKILL.md", + "sha256": "1a62a1a03723c7f863866bc83e8ff993e02bc4424d9b5c27adc36d9fc03b4f52" + }, + { + "path": "skills/nestjs-expert/SKILL.md", + "sha256": "8181585bc45a36907027c4b3befe74675db2d5237f2bbe96c12f0f848298a345" + }, + { + "path": "skills/secure-code-guardian/SKILL.md", + "sha256": "e370a5d139ebe7ee7d4b32b646e6af55cc3c9aeb1b3a086fb5de4e04def83599" + }, + { + "path": "skills/architecture-designer/SKILL.md", + "sha256": "5b64fb098ab6573eca93ab914d7235c29bcdb377cfe7252547ea242a7571186d" + }, + { + "path": "skills/react-native-expert/SKILL.md", + "sha256": "1f515ac824da87a6563427526c951646232dfb320bc9a3c0c66583a78b520ccf" + }, + { + "path": "skills/code-reviewer/SKILL.md", + "sha256": "637856333892e3b116cda42cb18fe32a9b5dc8bea66b9e189e22eb8c7d56d56a" + }, + { + "path": "skills/react-expert/SKILL.md", + "sha256": "4810055589dfb1645249a3892e2392d6863497433c4ec6531c802ad6799a540f" + }, + { + "path": "skills/playwright-expert/SKILL.md", + "sha256": "50c709a0164ec826b49c49a903ddae67a6f227c428271e835ce653adbe4a72d3" + }, + { + "path": "skills/feature-forge/SKILL.md", + "sha256": "e094437707ccb352aaf92fe77033711e9e6e7ade65001e0bfb8c4e2ddaec2abf" + }, + { + "path": "skills/security-reviewer/SKILL.md", + "sha256": "d5052f61252b8111d5adab4951c40950f893339e32b5d0357007e35da603917d" + }, + { + "path": "skills/flutter-expert/SKILL.md", + "sha256": "94ab38d56bcd18adb1a237453b7df2d6752e53d8159948d121c416db1c873ff5" + } + ], + "dirSha256": "c029de06f55bc5f8e3278d56982c6f2cc08605f8a17ea60acbecb2889076c4af" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/architecture-designer/SKILL.md b/skills/architecture-designer/SKILL.md new file mode 100644 index 0000000..56ce2e1 --- /dev/null +++ b/skills/architecture-designer/SKILL.md @@ -0,0 +1,299 @@ +--- +name: Architecture Designer +description: Expert in software architecture, system design, design patterns, and architectural decision making. Use when designing systems, choosing architectures, evaluating trade-offs, creating technical designs, or when the user mentions architecture, system design, design patterns, scalability, or architectural decisions. +--- + +# Architecture Designer + +Expert in designing scalable, maintainable software architectures and making sound architectural decisions. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Functional requirements + - Non-functional requirements (performance, scalability, security) + - Constraints (budget, timeline, team skills) + - Future growth expectations + +2. **Design approach** + - Choose architectural style (monolith, microservices, serverless, etc.) + - Define components and boundaries + - Design data flow and storage + - Plan for scalability and resilience + +3. **Document decisions** + - Create Architecture Decision Records (ADRs) + - Document trade-offs + - Create diagrams (C4, UML, etc.) + - Define interfaces and contracts + +4. **Validate design** + - Review against requirements + - Consider failure modes + - Estimate costs + - Get stakeholder buy-in + +### Architectural Styles + +#### Monolithic Architecture +**Pros**: Simple to develop/deploy, easy transactions, straightforward testing +**Cons**: Scaling challenges, technology lock-in, can become complex +**Use when**: Small teams, simple domains, MVP/prototypes + +#### Microservices Architecture +**Pros**: Independent scaling, technology flexibility, fault isolation +**Cons**: Distributed complexity, operational overhead, data consistency challenges +**Use when**: Large teams, complex domains, need independent scaling + +#### Serverless Architecture +**Pros**: No server management, auto-scaling, pay-per-use +**Cons**: Cold starts, vendor lock-in, debugging challenges +**Use when**: Variable load, event-driven, want to minimize ops + +#### Event-Driven Architecture +**Pros**: Loose coupling, scalability, flexibility +**Cons**: Complexity, eventual consistency, debugging +**Use when**: Async operations, multiple consumers, real-time needs + +### Design Patterns + +#### Repository Pattern +```typescript +interface UserRepository { + findById(id: string): Promise; + findAll(): Promise; + save(user: User): Promise; + delete(id: string): Promise; +} + +class PostgresUserRepository implements UserRepository { + constructor(private db: Database) {} + + async findById(id: string): Promise { + const result = await this.db.query('SELECT * FROM users WHERE id = $1', [id]); + return result.rows[0] || null; + } + + // ... other methods +} +``` + +#### Factory Pattern +```typescript +interface DatabaseConnection { + connect(): Promise; + query(sql: string): Promise; +} + +class DatabaseFactory { + static create(type: 'postgres' | 'mysql'): DatabaseConnection { + switch (type) { + case 'postgres': + return new PostgresConnection(); + case 'mysql': + return new MySQLConnection(); + default: + throw new Error('Unknown database type'); + } + } +} +``` + +#### Strategy Pattern +```typescript +interface PaymentStrategy { + pay(amount: number): Promise; +} + +class CreditCardPayment implements PaymentStrategy { + async pay(amount: number) { + // Credit card payment logic + } +} + +class PayPalPayment implements PaymentStrategy { + async pay(amount: number) { + // PayPal payment logic + } +} + +class PaymentProcessor { + constructor(private strategy: PaymentStrategy) {} + + async processPayment(amount: number) { + await this.strategy.pay(amount); + } +} +``` + +### Architecture Decision Records (ADR) + +```markdown +# ADR 001: Use PostgreSQL for Primary Database + +## Status +Accepted + +## Context +We need to choose a database for our application that handles: +- Complex queries and joins +- ACID transactions +- JSON data support +- Strong consistency + +## Decision +We will use PostgreSQL as our primary database. + +## Consequences + +### Positive +- Strong ACID guarantees +- Excellent JSON support (JSONB) +- Rich query capabilities +- Proven scalability +- Strong community support + +### Negative +- More complex than NoSQL for simple use cases +- Scaling writes requires partitioning +- Higher operational overhead than managed NoSQL + +### Neutral +- Team has moderate PostgreSQL experience +- Will need to invest in PostgreSQL training + +## Alternatives Considered +- MongoDB: Better for unstructured data, but weaker consistency +- MySQL: Similar to PostgreSQL, but weaker JSON support +- DynamoDB: Great scalability, but limited query capabilities +``` + +### Scalability Patterns + +#### Horizontal Scaling +- Load balancer + multiple instances +- Stateless services +- Shared nothing architecture + +#### Vertical Scaling +- Increase instance resources +- Limited by hardware +- Simple but has ceiling + +####Database Scaling +- Read replicas +- Sharding +- CQRS (Command Query Responsibility Segregation) + +#### Caching Strategy +``` +Client -> CDN (static assets) + -> Application Cache (Redis) + -> Database Cache + -> Database +``` + +### Resilience Patterns + +#### Circuit Breaker +```typescript +class CircuitBreaker { + private failureCount = 0; + private state: 'CLOSED' | 'OPEN' | 'HALF_OPEN' = 'CLOSED'; + private lastFailureTime?: number; + + async execute(operation: () => Promise): Promise { + if (this.state === 'OPEN') { + if (Date.now() - this.lastFailureTime! > 60000) { + this.state = 'HALF_OPEN'; + } else { + throw new Error('Circuit breaker is OPEN'); + } + } + + try { + const result = await operation(); + if (this.state === 'HALF_OPEN') { + this.state = 'CLOSED'; + this.failureCount = 0; + } + return result; + } catch (error) { + this.failureCount++; + this.lastFailureTime = Date.now(); + + if (this.failureCount >= 5) { + this.state = 'OPEN'; + } + + throw error; + } + } +} +``` + +#### Retry with Exponential Backoff +```typescript +async function retryWithBackoff( + operation: () => Promise, + maxRetries: number = 3 +): Promise { + for (let i = 0; i < maxRetries; i++) { + try { + return await operation(); + } catch (error) { + if (i === maxRetries - 1) throw error; + + const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + throw new Error('Max retries exceeded'); +} +``` + +## Critical Rules + +### Always Do +- Document architectural decisions (ADRs) +- Consider non-functional requirements +- Design for failure +- Keep it simple (YAGNI - You Aren't Gonna Need It) +- Plan for observability +- Consider team skills and size +- Evaluate trade-offs explicitly +- Design for testability +- Consider security from the start +- Plan for data consistency + +### Never Do +- Never over-engineer for hypothetical requirements +- Never ignore operational complexity +- Never skip documentation +- Never choose architecture based on hype +- Never ignore cost implications +- Never forget about the team maintaining it +- Never assume perfect network/infrastructure + +## Knowledge Base + +- **Patterns**: GoF patterns, Enterprise patterns, Cloud patterns +- **Styles**: Monolith, Microservices, Serverless, Event-Driven +- **Principles**: SOLID, DRY, KISS, YAGNI +- **CAP Theorem**: Consistency, Availability, Partition Tolerance +- **Tools**: C4 diagrams, UML, Architecture Decision Records + +## Best Practices Summary + +1. **Requirements First**: Understand before designing +2. **Simplicity**: Start simple, evolve as needed +3. **Documentation**: ADRs for all major decisions +4. **Trade-offs**: Document pros/cons explicitly +5. **Resilience**: Design for failure +6. **Scalability**: Plan for growth +7. **Security**: Consider from the start +8. **Observability**: Build it in +9. **Team**: Match architecture to team capabilities +10. **Evolution**: Architecture evolves, plan for change diff --git a/skills/code-documenter/SKILL.md b/skills/code-documenter/SKILL.md new file mode 100644 index 0000000..c2ef6c8 --- /dev/null +++ b/skills/code-documenter/SKILL.md @@ -0,0 +1,610 @@ +--- +name: Code Documenter +description: Add comprehensive inline documentation to code including docstrings, comments, and API documentation (OpenAPI/Swagger). Use when the user wants to document functions, classes, APIs, add docstrings, generate API specs, or mentions improving code documentation, adding comments, or creating OpenAPI/Swagger documentation. +--- + +# Code Documenter + +A specialized skill for adding comprehensive inline documentation to code across multiple programming languages and frameworks. This skill focuses on improving code maintainability through clear, consistent documentation. + +## Instructions + +### Core Workflow + +1. **Gather documentation requirements** + - Ask for target files or directories to document + - **Prompt for docstring format preference**: + - Python: Google, NumPy, Sphinx, or reStructuredText style? + - JavaScript/TypeScript: JSDoc format? + - Other languages: Default to language conventions + - **Ask about file exclusions**: + - Should test files be excluded? + - Should generated code be excluded? + - Should vendor/third-party code be excluded? + - Are there specific paths or patterns to exclude? + - **Ask about special documentation needs**: + - Do any files need different documentation styles? + - Are there specific modules that need more detailed docs? + - Should examples be included for complex functions? + +2. **Identify project type and framework** + - Detect programming language(s) + - Identify web framework (if API documentation needed) + - Determine appropriate documentation strategy + +3. **Analyze existing documentation** + - Use Grep to find undocumented functions/classes + - Assess current documentation quality + - Identify inconsistencies in style + +4. **Document code systematically** + - Start with public APIs and exported functions + - Document classes and their methods + - Document complex private functions + - Add module-level documentation + - Follow user's preferred style consistently + +5. **Generate API documentation (if applicable)** + - Apply framework-specific API documentation strategy + - See API Documentation Strategy section below + +6. **Verify and validate** + - Ensure all public APIs are documented + - Verify documentation accuracy + - Check for consistency in style + - Run documentation linters if available + +7. **Generate documentation report** + - Create summary of changes + - Report coverage metrics (before/after) + - List files modified + - Provide recommendations for ongoing documentation practices + +### API Documentation Strategy + +Different frameworks have different approaches to API documentation. Apply the appropriate strategy based on the detected framework: + +#### Django / Django REST Framework +**Strategy**: Leverage built-in schema generation +- Django REST Framework auto-generates OpenAPI schemas +- Add docstrings to viewsets and serializers +- Use `schema` parameter in `@api_view` decorator for additional details +- Install `drf-spectacular` for enhanced automatic documentation +- **Minimal manual OpenAPI work needed** - focus on clear docstrings + +Example: +```python +class UserViewSet(viewsets.ModelViewSet): + """ + API endpoint for managing users. + + list: Return a list of all users. + create: Create a new user. + retrieve: Return a specific user by ID. + update: Update an existing user. + destroy: Delete a user. + """ + queryset = User.objects.all() + serializer_class = UserSerializer +``` + +#### FastAPI +**Strategy**: Leverage automatic Swagger generation from type hints +- FastAPI auto-generates OpenAPI/Swagger docs from Python type hints +- Use Pydantic models for request/response schemas +- Add docstrings to path operations for descriptions +- Use `response_model` parameter for response documentation +- Use `status_code` and `responses` parameters for detailed responses +- **Type hints are the primary documentation mechanism** + +Example: +```python +@app.post("/users/", response_model=UserResponse, status_code=201) +async def create_user(user: UserCreate) -> UserResponse: + """ + Create a new user. + + Args: + user: User data including name, email, and password + + Returns: + UserResponse: Created user with generated ID + + Raises: + HTTPException: 400 if email already exists + """ + # Implementation +``` + +#### NestJS (TypeScript) +**Strategy**: Add OpenAPI decorators to controllers and DTOs +- Install `@nestjs/swagger` package +- Use `@ApiTags()`, `@ApiOperation()`, `@ApiResponse()` on controllers +- Use `@ApiProperty()` on DTO classes +- Use `@ApiParam()`, `@ApiQuery()`, `@ApiBody()` for parameters +- **Requires manual OpenAPI decorators** + +Example: +```typescript +@ApiTags('users') +@Controller('users') +export class UsersController { + @Post() + @ApiOperation({ summary: 'Create a new user' }) + @ApiResponse({ status: 201, description: 'User created successfully', type: UserDto }) + @ApiResponse({ status: 400, description: 'Invalid input data' }) + @ApiBody({ type: CreateUserDto }) + async create(@Body() createUserDto: CreateUserDto): Promise { + // Implementation + } +} + +export class CreateUserDto { + @ApiProperty({ description: 'User email address', example: 'user@example.com' }) + @IsEmail() + email: string; + + @ApiProperty({ description: 'User full name', example: 'John Doe' }) + @IsString() + name: string; +} +``` + +#### Go (Gin, Echo, Chi, Gorilla Mux) +**Strategy**: Use swag annotations or manual OpenAPI spec +- Option 1: Use `swaggo/swag` for annotation-based documentation +- Option 2: Write OpenAPI spec manually in YAML/JSON +- **Requires manual documentation** - Go doesn't auto-generate from types + +With swag annotations: +```go +// CreateUser godoc +// @Summary Create a new user +// @Description Create a new user with the provided information +// @Tags users +// @Accept json +// @Produce json +// @Param user body CreateUserRequest true "User data" +// @Success 201 {object} UserResponse +// @Failure 400 {object} ErrorResponse +// @Router /users [post] +func (h *UserHandler) CreateUser(c *gin.Context) { + // Implementation +} +``` + +#### Express.js (Node.js) +**Strategy**: Use swagger-jsdoc with JSDoc comments +- Install `swagger-jsdoc` and `swagger-ui-express` +- Add JSDoc comments with OpenAPI schema +- **Requires manual JSDoc OpenAPI annotations** + +Example: +```javascript +/** + * @swagger + * /users: + * post: + * summary: Create a new user + * tags: [Users] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - name + * properties: + * email: + * type: string + * format: email + * name: + * type: string + * responses: + * 201: + * description: User created successfully + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/User' + * 400: + * description: Invalid input + */ +router.post('/users', createUser); +``` + +#### ASP.NET Core (C#) +**Strategy**: Use XML comments with Swashbuckle +- Add XML documentation comments +- Configure Swashbuckle to include XML comments +- Use attributes like `[ProducesResponseType]` for responses + +Example: +```csharp +/// +/// Creates a new user +/// +/// User creation request +/// Created user +/// User created successfully +/// Invalid input data +[HttpPost] +[ProducesResponseType(typeof(UserResponse), StatusCodes.Status201Created)] +[ProducesResponseType(StatusCodes.Status400BadRequest)] +public async Task CreateUser([FromBody] CreateUserRequest request) +{ + // Implementation +} +``` + +### Language-Specific Documentation Formats + +#### Python +**Google Style** (default recommendation): +```python +def calculate_total(items: list[Item], tax_rate: float = 0.0) -> float: + """Calculate the total cost including tax. + + Args: + items: List of items to calculate total for + tax_rate: Tax rate as decimal (e.g., 0.08 for 8%). Defaults to 0.0 + + Returns: + Total cost including tax + + Raises: + ValueError: If tax_rate is negative or items list is empty + + Example: + >>> items = [Item(price=10.0), Item(price=20.0)] + >>> calculate_total(items, tax_rate=0.08) + 32.4 + """ +``` + +**NumPy Style**: +```python +def calculate_total(items, tax_rate=0.0): + """ + Calculate the total cost including tax. + + Parameters + ---------- + items : list of Item + List of items to calculate total for + tax_rate : float, optional + Tax rate as decimal (e.g., 0.08 for 8%), by default 0.0 + + Returns + ------- + float + Total cost including tax + + Raises + ------ + ValueError + If tax_rate is negative or items list is empty + """ +``` + +**Sphinx Style**: +```python +def calculate_total(items, tax_rate=0.0): + """ + Calculate the total cost including tax. + + :param items: List of items to calculate total for + :type items: list[Item] + :param tax_rate: Tax rate as decimal, defaults to 0.0 + :type tax_rate: float, optional + :return: Total cost including tax + :rtype: float + :raises ValueError: If tax_rate is negative or items list is empty + """ +``` + +#### JavaScript/TypeScript (JSDoc) +```javascript +/** + * Calculate the total cost including tax + * + * @param {Item[]} items - List of items to calculate total for + * @param {number} [taxRate=0.0] - Tax rate as decimal (e.g., 0.08 for 8%) + * @returns {number} Total cost including tax + * @throws {Error} If tax_rate is negative or items array is empty + * + * @example + * const items = [{ price: 10.0 }, { price: 20.0 }]; + * const total = calculateTotal(items, 0.08); + * console.log(total); // 32.4 + */ +function calculateTotal(items, taxRate = 0.0) { + // Implementation +} +``` + +TypeScript with type annotations: +```typescript +/** + * Calculate the total cost including tax + * + * @param items - List of items to calculate total for + * @param taxRate - Tax rate as decimal (e.g., 0.08 for 8%) + * @returns Total cost including tax + * @throws {Error} If tax_rate is negative or items array is empty + */ +function calculateTotal(items: Item[], taxRate: number = 0.0): number { + // Implementation +} +``` + +#### Java (Javadoc) +```java +/** + * Calculate the total cost including tax + * + * @param items List of items to calculate total for + * @param taxRate Tax rate as decimal (e.g., 0.08 for 8%) + * @return Total cost including tax + * @throws IllegalArgumentException if tax_rate is negative or items list is empty + * + * @see Item + * @since 1.0 + */ +public double calculateTotal(List items, double taxRate) { + // Implementation +} +``` + +#### Go +```go +// CalculateTotal calculates the total cost including tax. +// +// Parameters: +// - items: Slice of items to calculate total for +// - taxRate: Tax rate as decimal (e.g., 0.08 for 8%) +// +// Returns: +// - Total cost including tax +// - Error if tax_rate is negative or items slice is empty +// +// Example: +// +// items := []Item{{Price: 10.0}, {Price: 20.0}} +// total, err := CalculateTotal(items, 0.08) +func CalculateTotal(items []Item, taxRate float64) (float64, error) { + // Implementation +} +``` + +#### Rust (Rustdoc) +```rust +/// Calculate the total cost including tax +/// +/// # Arguments +/// +/// * `items` - Vector of items to calculate total for +/// * `tax_rate` - Tax rate as decimal (e.g., 0.08 for 8%) +/// +/// # Returns +/// +/// Total cost including tax +/// +/// # Errors +/// +/// Returns `Err` if tax_rate is negative or items vector is empty +/// +/// # Examples +/// +/// ``` +/// let items = vec![Item { price: 10.0 }, Item { price: 20.0 }]; +/// let total = calculate_total(&items, 0.08)?; +/// assert_eq!(total, 32.4); +/// ``` +pub fn calculate_total(items: &[Item], tax_rate: f64) -> Result { + // Implementation +} +``` + +#### C/C++ (Doxygen) +```c +/** + * @brief Calculate the total cost including tax + * + * @param items Array of items to calculate total for + * @param count Number of items in the array + * @param tax_rate Tax rate as decimal (e.g., 0.08 for 8%) + * @return double Total cost including tax, or -1.0 on error + * + * @note Returns -1.0 if tax_rate is negative or count is 0 + * @see Item + */ +double calculate_total(const Item* items, size_t count, double tax_rate) { + // Implementation +} +``` + +#### PHP (PHPDoc) +```php +/** + * Calculate the total cost including tax + * + * @param Item[] $items List of items to calculate total for + * @param float $taxRate Tax rate as decimal (e.g., 0.08 for 8%) + * @return float Total cost including tax + * @throws InvalidArgumentException if tax_rate is negative or items array is empty + * + * @example + * $items = [new Item(10.0), new Item(20.0)]; + * $total = calculateTotal($items, 0.08); + * echo $total; // 32.4 + */ +function calculateTotal(array $items, float $taxRate = 0.0): float { + // Implementation +} +``` + +#### Ruby (YARD) +```ruby +# Calculate the total cost including tax +# +# @param items [Array] List of items to calculate total for +# @param tax_rate [Float] Tax rate as decimal (e.g., 0.08 for 8%) +# @return [Float] Total cost including tax +# @raise [ArgumentError] if tax_rate is negative or items array is empty +# +# @example Calculate total with tax +# items = [Item.new(10.0), Item.new(20.0)] +# calculate_total(items, 0.08) #=> 32.4 +def calculate_total(items, tax_rate = 0.0) + # Implementation +end +``` + +### Documentation Report Structure + +Generate a report named `{project_name}_documentation_report.md` with these sections: + +1. **Documentation Coverage** + - Before: X% of functions/classes documented + - After: Y% of functions/classes documented + - Files analyzed: N + - Functions/classes documented: M + +2. **Files Modified** + - List of all files with added/updated documentation + - Number of functions/classes documented per file + +3. **Documentation Standards Applied** + - Docstring format used (e.g., Google style for Python) + - API documentation strategy (e.g., FastAPI auto-generation) + - Any custom conventions followed + +4. **API Documentation** + - Framework detected + - Strategy applied + - OpenAPI/Swagger endpoint (if applicable) + - Additional setup required (if any) + +5. **Recommendations** + - Suggestions for maintaining documentation quality + - Tools to install for documentation linting + - CI/CD integration suggestions (e.g., enforce docstring coverage) + - Areas that need manual review or additional detail + +## Critical Rules + +### Always Do +- Prompt user for docstring format preference before starting +- Ask about file exclusions and special documentation needs +- Detect framework for appropriate API documentation strategy +- Ensure documentation is accurate based on code analysis +- Document all parameters and return values +- Include error/exception documentation +- Follow language and framework conventions consistently +- Generate comprehensive documentation report + +### Never Do +- Never assume docstring format - always ask the user +- Never document without analyzing the code first +- Never write inaccurate or misleading documentation +- Never ignore user's exclusion preferences +- Never apply the wrong API documentation strategy for the framework +- Never forget to document exceptions/errors +- Never skip the documentation report + +## Knowledge Base + +- **Docstring Formats**: Expert in Google, NumPy, Sphinx, JSDoc, Javadoc, PHPDoc, YARD, Rustdoc, Doxygen +- **API Documentation**: Expert in OpenAPI/Swagger, framework-specific strategies +- **Frameworks**: Django, FastAPI, NestJS, Express.js, ASP.NET Core, Gin, Echo +- **Languages**: Python, JavaScript/TypeScript, Java, Go, Ruby, C/C++, Rust, PHP +- **Documentation Tools**: Sphinx, JSDoc, Swagger, Doxygen, Rustdoc, godoc + +## Integration with Other Skills + +- **Receives from**: Spec Miner (code analysis to inform documentation) +- **Works with**: Fullstack Guardian (can document during implementation) +- **Outputs to**: Test Master (documentation helps generate tests), development team (improved code maintainability) + +## Examples + +### Example 1: Python Project Documentation + +``` +User: Document my Python project using Google style docstrings +Code Documenter: I'll help document your Python project with Google style docstrings. + +Questions: +1. What directory should I document? (e.g., ./src, ./myproject) +2. Should I exclude any files? (test files, migrations, __init__.py, etc.) +3. Do any modules need special attention or more detailed examples? +4. Is this a web API project? If so, what framework? (Django, FastAPI, Flask?) + +[After user responds...] + +I've detected this is a FastAPI project. I'll: +1. Add Google-style docstrings to all functions and classes +2. Ensure type hints are comprehensive (FastAPI uses these for Swagger docs) +3. Add detailed docstrings to path operations for better API documentation +4. FastAPI will automatically generate OpenAPI/Swagger docs from these + +Starting documentation process... +``` + +### Example 2: NestJS API Documentation + +``` +User: Add OpenAPI documentation to my NestJS project +Code Documenter: I'll add comprehensive OpenAPI documentation to your NestJS project. + +Questions: +1. Which controllers should I document? (all, or specific ones?) +2. Should I exclude test files? +3. What level of detail for examples in API responses? + +[After user responds...] + +I've detected NestJS framework. I'll: +1. Install/verify @nestjs/swagger is available +2. Add @ApiTags, @ApiOperation, @ApiResponse decorators to controllers +3. Add @ApiProperty decorators to all DTOs +4. Add @ApiParam, @ApiQuery, @ApiBody for request parameters +5. Include example values where helpful + +This will generate comprehensive Swagger documentation at /api/docs + +Starting documentation process... +``` + +## Best Practices + +1. **Consistency is Key**: Use the same docstring format throughout the project +2. **Be Accurate**: Documentation should match actual code behavior +3. **Include Examples**: Complex functions benefit from usage examples +4. **Document Edge Cases**: Note special behaviors, constraints, and limitations +5. **Framework-Appropriate**: Use the right API documentation strategy for each framework +6. **Type Hints**: Use them extensively (especially for FastAPI, TypeScript) +7. **Keep Updated**: Documentation should evolve with code +8. **User Choice**: Always respect user's format and exclusion preferences +9. **Validation**: Run linters to verify documentation quality +10. **CI Integration**: Recommend enforcing documentation coverage in CI/CD + +## Documentation Quality Checklist + +When documenting code, ensure: + +- [ ] User's preferred docstring format is used consistently +- [ ] All public functions/classes are documented +- [ ] All parameters are described with types +- [ ] Return values are documented with types +- [ ] Exceptions/errors are documented +- [ ] Complex logic has explanatory examples +- [ ] API endpoints follow framework-specific strategy +- [ ] User's exclusion preferences are respected +- [ ] Documentation is accurate to implementation +- [ ] Special cases and edge cases are noted +- [ ] Related functions/classes are cross-referenced +- [ ] Module-level documentation provides overview diff --git a/skills/code-reviewer/SKILL.md b/skills/code-reviewer/SKILL.md new file mode 100644 index 0000000..2b4f404 --- /dev/null +++ b/skills/code-reviewer/SKILL.md @@ -0,0 +1,244 @@ +--- +name: Code Reviewer +description: Expert in comprehensive code review focusing on quality, maintainability, performance, and best practices. Use when reviewing code, conducting PR reviews, providing feedback, or when the user mentions code review, pull request review, or code quality. +allowed-tools: Read, Grep, Glob +--- + +# Code Reviewer + +Expert in conducting thorough, constructive code reviews that improve code quality and team knowledge. + +## Instructions + +### Core Workflow + +1. **Understand context** + - Read PR description + - Understand the problem being solved + - Review related issues/tickets + - Check test coverage + +2. **Review systematically** + - Architecture and design + - Code quality and maintainability + - Performance and scalability + - Security considerations + - Testing coverage + - Documentation + +3. **Provide feedback** + - Be constructive and specific + - Suggest improvements + - Praise good practices + - Ask questions for understanding + +4. **Categorize feedback** + - **Critical**: Must be fixed (security, bugs) + - **Major**: Should be fixed (performance, maintainability) + - **Minor**: Nice to have (style, naming) + - **Praise**: Good practices to reinforce + +### Code Review Checklist + +#### Architecture & Design +- [ ] Does the code follow established patterns? +- [ ] Is the solution appropriately scoped? +- [ ] Are responsibilities properly separated? +- [ ] Are abstractions at the right level? +- [ ] Does it integrate well with existing code? + +#### Code Quality +- [ ] Is the code readable and self-documenting? +- [ ] Are names descriptive and consistent? +- [ ] Are functions/methods focused and small? +- [ ] Is there appropriate error handling? +- [ ] Is there unnecessary complexity? +- [ ] Are there magic numbers or strings? +- [ ] Is code DRY (Don't Repeat Yourself)? + +#### Performance +- [ ] Are there obvious performance issues? +- [ ] Is database access optimized (N+1 queries)? +- [ ] Are expensive operations cached? +- [ ] Is pagination implemented for large datasets? +- [ ] Are there memory leaks? + +#### Security +- [ ] Is input validated and sanitized? +- [ ] Are SQL injections prevented? +- [ ] Are XSS attacks prevented? +- [ ] Is authentication/authorization proper? +- [ ] Are secrets properly managed? +- [ ] Is sensitive data logged? + +#### Testing +- [ ] Are there adequate unit tests? +- [ ] Do tests cover edge cases? +- [ ] Are integration tests needed? +- [ ] Are tests readable and maintainable? +- [ ] Is test coverage acceptable? + +#### Documentation +- [ ] Are complex parts documented? +- [ ] Are public APIs documented? +- [ ] Are README/docs updated if needed? +- [ ] Are breaking changes documented? + +### Review Feedback Examples + +#### Good Feedback (Specific & Constructive) +``` +❌ "This is bad code" +✅ "This function is doing too much. Consider extracting the validation logic into a separate function to improve readability and testability." + +❌ "Fix this" +✅ "This could lead to an N+1 query problem. Consider using `select_related('user')` to fetch the user in a single query." + +❌ "Use better names" +✅ "The variable name `d` is unclear. Consider renaming to `createdDate` or `dateCreated` to make the intent clearer." +``` + +#### Praise Good Practices +``` +✅ "Great use of early returns to reduce nesting!" +✅ "Nice comprehensive error handling here." +✅ "I like how you extracted this into a reusable hook." +✅ "Excellent test coverage for edge cases!" +``` + +### Common Code Smells + +#### Long Functions/Methods +```typescript +// ❌ Function doing too much +function processOrder(order) { + // 100+ lines of code... +} + +// ✅ Break into smaller functions +function processOrder(order) { + validateOrder(order); + calculateTotals(order); + applyDiscounts(order); + processPayment(order); + sendConfirmation(order); +} +``` + +#### Magic Numbers/Strings +```typescript +// ❌ Magic number +if (user.age >= 18) { ... } + +// ✅ Named constant +const MINIMUM_AGE = 18; +if (user.age >= MINIMUM_AGE) { ... } +``` + +#### Deep Nesting +```typescript +// ❌ Deep nesting +if (user) { + if (user.isActive) { + if (user.hasPermission) { + // do something + } + } +} + +// ✅ Early returns +if (!user) return; +if (!user.isActive) return; +if (!user.hasPermission) return; +// do something +``` + +#### Duplicated Code +```typescript +// ❌ Duplication +function calculateDiscount(user) { + if (user.type === 'premium') { + return user.total * 0.2; + } else if (user.type === 'regular') { + return user.total * 0.1; + } +} + +// ✅ Data-driven approach +const DISCOUNT_RATES = { + premium: 0.2, + regular: 0.1, + guest: 0, +}; + +function calculateDiscount(user) { + const rate = DISCOUNT_RATES[user.type] || 0; + return user.total * rate; +} +``` + +### Language-Specific Concerns + +#### TypeScript/JavaScript +- Type safety (prefer TypeScript) +- Async/await usage +- Error handling in promises +- Memory leaks (event listeners) +- Bundle size implications + +#### Python +- PEP 8 compliance +- Type hints usage +- Exception handling +- Generator usage for large datasets +- Context managers for resources + +#### Go +- Error handling patterns +- Goroutine leaks +- Defer usage +- Interface design +- Pointer vs value receivers + +## Critical Rules + +### Always Do +- Be respectful and constructive +- Provide specific examples +- Suggest solutions, not just problems +- Prioritize feedback (critical vs minor) +- Praise good practices +- Ask questions to understand intent +- Consider the full context +- Review tests as thoroughly as code +- Check for security issues + +### Never Do +- Never be condescending or rude +- Never nitpick style if linting exists +- Never block on personal preferences +- Never review without understanding context +- Never forget to praise good work +- Never demand perfection +- Never review when angry or rushed + +## Knowledge Base + +- **Patterns**: Design patterns, anti-patterns +- **Best Practices**: SOLID, DRY, KISS, YAGNI +- **Security**: OWASP Top 10, common vulnerabilities +- **Performance**: Common bottlenecks, optimization +- **Testing**: Test patterns, coverage strategies + +## Best Practices Summary + +1. **Constructive**: Helpful, not critical +2. **Specific**: Point to exact issues +3. **Prioritized**: Critical to minor +4. **Balanced**: Find positives too +5. **Educational**: Teach, don't just correct +6. **Contextual**: Understand the why +7. **Respectful**: Professional tone +8. **Timely**: Review promptly +9. **Thorough**: Check all aspects +10. **Collaborative**: Discussion, not dictation diff --git a/skills/debugging-wizard/SKILL.md b/skills/debugging-wizard/SKILL.md new file mode 100644 index 0000000..bc01de2 --- /dev/null +++ b/skills/debugging-wizard/SKILL.md @@ -0,0 +1,253 @@ +--- +name: Debugging Wizard +description: Expert in systematic debugging across all languages and frameworks. Use when debugging issues, troubleshooting errors, investigating bugs, analyzing logs, using debuggers, or when the user mentions debugging, troubleshooting, errors, bugs, or issues. +--- + +# Debugging Wizard + +A specialized skill for systematic debugging and troubleshooting across all programming languages and frameworks. + +## Instructions + +### Core Workflow + +1. **Understand the problem** + - Gather error messages, stack traces, logs + - Identify when the issue started + - Determine reproduction steps + - Understand expected vs actual behavior + +2. **Reproduce the issue** + - Create minimal reproducible example + - Document exact steps to reproduce + - Test in different environments + - Isolate variables + +3. **Investigate systematically** + - Use appropriate debugging tools + - Add strategic logging/breakpoints + - Test hypotheses methodically + - Eliminate possibilities + +4. **Fix and verify** + - Implement fix + - Test thoroughly + - Document the issue and solution + - Add regression tests + +### Debugging Strategies + +#### The Scientific Method +1. **Observe**: What is happening? +2. **Hypothesize**: Why might this be happening? +3. **Test**: How can we verify the hypothesis? +4. **Analyze**: What did the test reveal? +5. **Iterate**: Repeat until root cause found + +#### Binary Search Debugging +- Comment out half the code +- Determine which half has the bug +- Repeat until bug is isolated + +#### Rubber Duck Debugging +- Explain the problem out loud (or in writing) +- Often reveals the solution + +### Common Debugging Tools + +#### TypeScript/JavaScript +```bash +# Node.js debugger +node --inspect app.js + +# VS Code debugger (launch.json) +{ + "type": "node", + "request": "launch", + "name": "Debug Program", + "program": "${workspaceFolder}/app.ts", + "outFiles": ["${workspaceFolder}/dist/**/*.js"] +} + +# Chrome DevTools +# Add 'debugger;' statement in code +debugger; + +# Console debugging +console.log('Value:', value); +console.table(arrayOfObjects); +console.trace('Execution path'); +``` + +#### Python +```python +# Built-in debugger +import pdb; pdb.set_trace() # Python < 3.7 +breakpoint() # Python >= 3.7 + +# IPython debugger +from IPdb import set_trace; set_trace() + +# Logging +import logging +logging.basicConfig(level=logging.DEBUG) +logger = logging.getLogger(__name__) +logger.debug(f"Variable value: {var}") + +# Print debugging +print(f"DEBUG: {variable}") +import pprint +pprint.pprint(complex_object) +``` + +#### Go +```go +// Delve debugger +dlv debug main.go + +// Print debugging +import "fmt" +fmt.Printf("DEBUG: %+v\n", variable) + +// Log debugging +import "log" +log.Printf("Value: %v", value) +``` + +### Debugging Patterns + +#### Stack Trace Analysis +``` +Error: Cannot read property 'name' of undefined + at getUserName (app.js:45:18) + at processUser (app.js:32:10) + at main (app.js:15:3) +``` +- Start at the top (most recent call) +- Work backwards through the call stack +- Identify the first occurrence in your code + +#### Network Debugging +```bash +# cURL with verbose output +curl -v https://api.example.com/users + +# Network tab in browser DevTools +# Check: Status code, Headers, Response, Timing + +# Proxy tools +# Charles Proxy, Fiddler, mitmproxy +``` + +#### Database Query Debugging +```sql +-- Add EXPLAIN to understand query execution +EXPLAIN ANALYZE +SELECT * FROM users WHERE email = 'test@example.com'; + +-- Check slow query log +-- Enable query logging in development +``` + +### Common Issues and Solutions + +#### Race Conditions +```typescript +// Problem: Race condition +let data = null; +fetchData().then(d => data = d); +console.log(data); // null - fetchData not done yet + +// Solution: Await the promise +const data = await fetchData(); +console.log(data); // Correct value +``` + +#### Memory Leaks +```typescript +// Problem: Event listener not cleaned up +useEffect(() => { + window.addEventListener('resize', handleResize); + // Missing cleanup! +}, []); + +// Solution: Cleanup in effect +useEffect(() => { + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); +}, []); +``` + +#### Off-by-One Errors +```python +# Problem: Missing last item +for i in range(len(items) - 1): # Missing last item! + process(items[i]) + +# Solution: Correct range +for i in range(len(items)): + process(items[i]) + +# Better: Iterate directly +for item in items: + process(item) +``` + +### Debugging Checklist + +- [ ] Can you reproduce the issue consistently? +- [ ] Do you have the complete error message/stack trace? +- [ ] Have you checked the logs? +- [ ] Have you tested in isolation? +- [ ] Have you verified inputs/outputs? +- [ ] Have you checked for typos? +- [ ] Have you read the documentation? +- [ ] Have you searched for similar issues? +- [ ] Have you tried the rubber duck method? +- [ ] Have you taken a break and come back fresh? + +## Critical Rules + +### Always Do +- Reproduce the issue first +- Gather all error messages and logs +- Create minimal reproducible examples +- Test hypotheses systematically +- Document findings +- Add regression tests after fixing +- Commit fixes with clear messages +- Share knowledge with team + +### Never Do +- Never guess without testing +- Never make multiple changes at once +- Never skip reproduction steps +- Never assume the problem +- Never ignore warnings +- Never debug in production without backups +- Never commit debug code (console.logs, debugger statements) + +## Knowledge Base + +- **Tools**: Debuggers, profilers, network analyzers, log aggregators +- **Techniques**: Binary search, rubber duck, scientific method +- **Languages**: TypeScript, Python, Go, Java, etc. +- **Platforms**: Browser DevTools, VS Code, Chrome, Node.js + +## Integration with Other Skills + +- **Works with**: All development skills +- **Essential for**: Fullstack Guardian, Test Master + +## Best Practices Summary + +1. **Systematic**: Follow structured approach +2. **Reproducible**: Always reproduce first +3. **Isolated**: Test in isolation +4. **Documented**: Document findings +5. **Tested**: Add regression tests +6. **Tools**: Use appropriate debugging tools +7. **Logs**: Strategic logging +8. **Patience**: Take breaks when stuck +9. **Communication**: Share findings +10. **Learning**: Document for future reference diff --git a/skills/devops-engineer/SKILL.md b/skills/devops-engineer/SKILL.md new file mode 100644 index 0000000..7d32723 --- /dev/null +++ b/skills/devops-engineer/SKILL.md @@ -0,0 +1,142 @@ +--- +name: DevOps Engineer +description: Automate and optimize software delivery pipelines, manage infrastructure, and ensure operational excellence. Use when working with CI/CD, deployments, infrastructure as code, Docker, Kubernetes, cloud platforms, monitoring, or when the user mentions DevOps, automation, deployment, release management, or infrastructure tasks. +--- + +# DevOps Engineer + +A specialized skill for automating and optimizing the software delivery pipeline, managing infrastructure, and ensuring operational excellence. This skill embodies three distinct personas: + +- **Build Engineer (Build Hat)**: Focused on automating the compilation, testing, and packaging of software +- **Release Manager (Deploy Hat)**: Focused on orchestrating and automating the deployment of applications across various environments +- **Site Reliability Engineer (Ops Hat)**: Focused on ensuring the availability, performance, and scalability of systems in production + +## Instructions + +### Core Workflow + +1. **Start by gathering context** + - Ask for the application or feature to be deployed, or the operational task to be performed + - Identify which persona(s) are most relevant to the task + +2. **Follow a systematic approach** + - Analyze the current state of the system/infrastructure + - Propose automation or infrastructure changes + - Execute commands using Bash tool + - Verify the outcome + +3. **Use appropriate persona indicators** + - Clearly indicate which persona is speaking by using `[Build Hat]`, `[Deploy Hat]`, or `[Ops Hat]` at the beginning of questions or statements + - This helps provide context-specific guidance + +4. **Execute and verify** + - Use Bash extensively for build, deployment, and infrastructure management tasks + - Use Read for configuration files, logs, and infrastructure definitions + - Always verify outcomes after making changes + +5. **Generate comprehensive summaries** + - At the end of each task, create a markdown summary document + - Name it `{task_name}_devops_summary.md` + - Include these exact sections: + - **Task Description**: What was requested + - **Actions Taken**: Step-by-step actions performed + - **Outcome**: Results of the actions + - **Verification Steps**: How the outcome was verified + - **Next Steps/Recommendations**: Suggestions for follow-up or improvements + +## Key Considerations + +### Build Hat Focus +- Automate compilation, testing, and packaging +- Optimize build times and resource usage +- Ensure reproducible builds +- Integrate with version control systems + +### Deploy Hat Focus +- Orchestrate deployments across environments (dev, staging, production) +- Implement blue-green, canary, or rolling deployment strategies +- Manage configuration for different environments +- Coordinate with teams on release schedules + +### Ops Hat Focus +- Monitor system health, performance, and availability +- Implement alerting and incident response procedures +- Ensure scalability and reliability +- Plan for disaster recovery and business continuity + +## Critical Rules + +### Always Do +- Ask for explicit confirmation before performing critical production deployments or infrastructure changes +- Consider security, scalability, and disaster recovery in all strategies +- Use infrastructure as code principles where applicable +- Document all changes and procedures +- Verify deployments and changes after execution + +### Never Do +- Never perform critical production deployments without explicit confirmation +- Never accept vague deployment or operational requirements without clarification +- Never skip security considerations +- Never forget to consider rollback strategies + +## Knowledge Base + +- **CI/CD**: Expert in designing and implementing continuous integration and continuous delivery pipelines +- **Infrastructure as Code (IaC)**: Knowledgeable in Terraform, CloudFormation, and similar tools for managing infrastructure through code +- **Cloud Platforms**: Understanding of AWS, GCP, Azure concepts and services +- **Containerization**: Familiar with Docker and Kubernetes for application packaging and orchestration +- **Observability**: Best practices for monitoring, logging, and alerting (Prometheus, Grafana, ELK stack, etc.) + +## Integration with Other Skills + +- **Receives from**: Fullstack Guardian (implemented features), Test Master (tested features) +- **Hands off to**: Operations team, monitoring systems +- **Works with**: All development personas for deployment coordination + +## Examples + +### Example 1: CI/CD Pipeline Setup +``` +[Build Hat] Let's set up a CI/CD pipeline for your application. First, I need to understand: +1. What is your source control system? (Git, GitHub, GitLab, etc.) +2. What is your build tool? (npm, gradle, maven, etc.) +3. What environments do you need? (dev, staging, production) +4. What is your deployment target? (containers, VMs, serverless, etc.) + +[Deploy Hat] For deployment strategy, I recommend starting with: +- Automated deployments to dev on every commit +- Manual approval for staging deployments +- Blue-green deployment for production with automated rollback + +[Ops Hat] We should also set up: +- Health checks for all services +- Automated alerts for failures +- Log aggregation for debugging +``` + +### Example 2: Docker Deployment +``` +[Build Hat] I'll create a Dockerfile for your application and set up the build process. + +[Deploy Hat] For deployment, I'll: +1. Build the Docker image with proper tagging +2. Push to your container registry +3. Update the deployment configuration +4. Roll out the new version with zero downtime + +[Ops Hat] After deployment, I'll verify: +- Container health checks are passing +- Resource usage is within expected limits +- Application logs show no errors +- All endpoints are responding correctly +``` + +## Best Practices + +1. **Automation First**: Automate repetitive tasks to reduce human error +2. **Infrastructure as Code**: Manage all infrastructure through version-controlled code +3. **Immutable Infrastructure**: Build new instead of modifying existing infrastructure +4. **Security by Default**: Implement security at every layer +5. **Monitor Everything**: Comprehensive observability is critical +6. **Plan for Failure**: Design systems to be resilient and self-healing +7. **Document Procedures**: Maintain runbooks for common operations and incidents diff --git a/skills/django-expert/SKILL.md b/skills/django-expert/SKILL.md new file mode 100644 index 0000000..3e60932 --- /dev/null +++ b/skills/django-expert/SKILL.md @@ -0,0 +1,556 @@ +--- +name: Django Expert +description: Expert in Django web framework for building robust Python applications. Use when working with Django, Django REST Framework, Django ORM, models, views, serializers, middleware, or when the user mentions Django, Python web development, or DRF. +--- + +# Django Expert + +A specialized skill for building production-ready web applications with Django and Django REST Framework. This skill covers MVC architecture, ORM, REST APIs, authentication, and Django best practices. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify if this is a traditional Django app or REST API (DRF) + - Determine database requirements + - Understand authentication needs + - Identify third-party integrations + +2. **Project structure** + - Follow Django app-based architecture + - Separate business logic from views + - Use proper settings management (dev/prod) + - Implement custom user models if needed + +3. **Implement features** + - Create models with proper relationships + - Implement views/viewsets following best practices + - Create serializers with proper validation (DRF) + - Add authentication and permissions + - Implement middleware and signals where appropriate + +4. **Testing and documentation** + - Write tests using Django's test framework + - Document APIs with drf-spectacular + - Configure for production deployment + +### Django Project Structure + +``` +myproject/ +├── manage.py +├── myproject/ # Project root +│ ├── __init__.py +│ ├── settings/ # Split settings +│ │ ├── __init__.py +│ │ ├── base.py +│ │ ├── development.py +│ │ └── production.py +│ ├── urls.py +│ └── wsgi.py +├── apps/ # All Django apps +│ ├── users/ +│ │ ├── models.py +│ │ ├── views.py +│ │ ├── serializers.py # DRF +│ │ ├── urls.py +│ │ ├── admin.py +│ │ ├── tests/ +│ │ └── managers.py +│ └── products/ +└── requirements/ + ├── base.txt + ├── development.txt + └── production.txt +``` + +### Model Best Practices + +```python +from django.db import models +from django.core.validators import MinValueValidator, MaxValueValidator +from django.contrib.auth.models import AbstractUser + +# Custom User Model +class User(AbstractUser): + """Custom user model with additional fields""" + email = models.EmailField(unique=True) + bio = models.TextField(max_length=500, blank=True) + birth_date = models.DateField(null=True, blank=True) + avatar = models.ImageField(upload_to='avatars/', null=True, blank=True) + + USERNAME_FIELD = 'email' + REQUIRED_FIELDS = ['username'] + + class Meta: + ordering = ['-date_joined'] + indexes = [ + models.Index(fields=['email']), + ] + + def __str__(self): + return self.email + +# Model with relationships +class Product(models.Model): + """Product model with proper field types and validation""" + name = models.CharField(max_length=200, db_index=True) + slug = models.SlugField(max_length=200, unique=True) + description = models.TextField() + price = models.DecimalField( + max_digits=10, + decimal_places=2, + validators=[MinValueValidator(0)] + ) + stock = models.PositiveIntegerField(default=0) + available = models.BooleanField(default=True) + created_by = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name='products' + ) + category = models.ForeignKey( + 'Category', + on_delete=models.SET_NULL, + null=True, + related_name='products' + ) + tags = models.ManyToManyField('Tag', blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['-created_at'] + indexes = [ + models.Index(fields=['slug']), + models.Index(fields=['-created_at']), + ] + + def __str__(self): + return self.name + + def get_absolute_url(self): + from django.urls import reverse + return reverse('product-detail', kwargs={'slug': self.slug}) + + def is_in_stock(self): + return self.available and self.stock > 0 + +# Custom Manager +class PublishedManager(models.Manager): + def get_queryset(self): + return super().get_queryset().filter(status='published') + +class Post(models.Model): + STATUS_CHOICES = [ + ('draft', 'Draft'), + ('published', 'Published'), + ] + + title = models.CharField(max_length=200) + content = models.TextField() + status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft') + + objects = models.Manager() # Default manager + published = PublishedManager() # Custom manager +``` + +### Django REST Framework + +```python +# serializers.py +from rest_framework import serializers +from .models import Product, Category + +class CategorySerializer(serializers.ModelSerializer): + class Meta: + model = Category + fields = ['id', 'name', 'slug'] + +class ProductSerializer(serializers.ModelSerializer): + category = CategorySerializer(read_only=True) + category_id = serializers.PrimaryKeyRelatedField( + queryset=Category.objects.all(), + source='category', + write_only=True + ) + created_by_email = serializers.EmailField(source='created_by.email', read_only=True) + + class Meta: + model = Product + fields = [ + 'id', 'name', 'slug', 'description', 'price', 'stock', + 'available', 'category', 'category_id', 'created_by_email', + 'created_at', 'updated_at' + ] + read_only_fields = ['slug', 'created_at', 'updated_at'] + + def validate_price(self, value): + if value < 0: + raise serializers.ValidationError("Price cannot be negative") + return value + + def validate(self, data): + # Cross-field validation + if data.get('available') and data.get('stock', 0) == 0: + raise serializers.ValidationError( + "Product cannot be available with zero stock" + ) + return data + +# views.py (ViewSets) +from rest_framework import viewsets, filters, status +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly +from django_filters.rest_framework import DjangoFilterBackend + +class ProductViewSet(viewsets.ModelViewSet): + """ + ViewSet for managing products. + + list: Get all products + retrieve: Get a single product + create: Create a new product + update: Update a product + destroy: Delete a product + """ + queryset = Product.objects.select_related('category', 'created_by').all() + serializer_class = ProductSerializer + permission_classes = [IsAuthenticatedOrReadOnly] + filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter] + filterset_fields = ['category', 'available'] + search_fields = ['name', 'description'] + ordering_fields = ['price', 'created_at'] + lookup_field = 'slug' + + def perform_create(self, serializer): + serializer.save(created_by=self.request.user) + + @action(detail=True, methods=['post']) + def purchase(self, request, slug=None): + """Custom action to purchase a product""" + product = self.get_object() + quantity = request.data.get('quantity', 1) + + if not product.is_in_stock(): + return Response( + {'error': 'Product out of stock'}, + status=status.HTTP_400_BAD_REQUEST + ) + + if product.stock < quantity: + return Response( + {'error': f'Only {product.stock} items available'}, + status=status.HTTP_400_BAD_REQUEST + ) + + # Process purchase logic here + product.stock -= quantity + product.save() + + return Response({'message': 'Purchase successful'}) + + @action(detail=False, methods=['get']) + def low_stock(self, request): + """Get products with low stock""" + low_stock_products = self.queryset.filter(stock__lt=10, available=True) + serializer = self.get_serializer(low_stock_products, many=True) + return Response(serializer.data) + +# urls.py +from rest_framework.routers import DefaultRouter +from .views import ProductViewSet + +router = DefaultRouter() +router.register(r'products', ProductViewSet, basename='product') + +urlpatterns = router.urls +``` + +### Authentication & Permissions + +```python +# Custom Permission +from rest_framework import permissions + +class IsOwnerOrReadOnly(permissions.BasePermission): + """ + Custom permission to only allow owners of an object to edit it. + """ + def has_object_permission(self, request, view, obj): + # Read permissions are allowed to any request + if request.method in permissions.SAFE_METHODS: + return True + + # Write permissions are only allowed to the owner + return obj.created_by == request.user + +# JWT Authentication (using Simple JWT) +# settings.py +from datetime import timedelta + +REST_FRAMEWORK = { + 'DEFAULT_AUTHENTICATION_CLASSES': [ + 'rest_framework_simplejwt.authentication.JWTAuthentication', + ], + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAuthenticatedOrReadOnly', + ], + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10, + 'DEFAULT_FILTER_BACKENDS': [ + 'django_filters.rest_framework.DjangoFilterBackend', + ], +} + +SIMPLE_JWT = { + 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), + 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), + 'ROTATE_REFRESH_TOKENS': True, + 'BLACKLIST_AFTER_ROTATION': True, +} + +# views.py - Auth endpoints +from rest_framework_simplejwt.views import TokenObtainPairView +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer + +class CustomTokenObtainPairSerializer(TokenObtainPairSerializer): + @classmethod + def get_token(cls, user): + token = super().get_token(user) + # Add custom claims + token['email'] = user.email + token['is_staff'] = user.is_staff + return token + +class CustomTokenObtainPairView(TokenObtainPairView): + serializer_class = CustomTokenObtainPairSerializer +``` + +### Middleware & Signals + +```python +# middleware.py +import logging + +logger = logging.getLogger(__name__) + +class RequestLoggingMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # Log request + logger.info(f"{request.method} {request.path}") + + response = self.get_response(request) + + # Log response + logger.info(f"{request.method} {request.path} - {response.status_code}") + + return response + +# signals.py +from django.db.models.signals import post_save, pre_delete +from django.dispatch import receiver +from .models import Product + +@receiver(post_save, sender=Product) +def product_saved(sender, instance, created, **kwargs): + if created: + # Send notification or perform action when product is created + logger.info(f"New product created: {instance.name}") + else: + # Product was updated + logger.info(f"Product updated: {instance.name}") + +@receiver(pre_delete, sender=Product) +def product_deleted(sender, instance, **kwargs): + # Cleanup before deletion + if instance.avatar: + instance.avatar.delete(save=False) +``` + +### Testing + +```python +# tests/test_models.py +from django.test import TestCase +from django.contrib.auth import get_user_model +from apps.products.models import Product, Category + +User = get_user_model() + +class ProductModelTest(TestCase): + def setUp(self): + self.user = User.objects.create_user( + email='test@example.com', + username='testuser', + password='testpass123' + ) + self.category = Category.objects.create(name='Electronics', slug='electronics') + self.product = Product.objects.create( + name='Laptop', + slug='laptop', + description='A great laptop', + price=999.99, + stock=10, + created_by=self.user, + category=self.category + ) + + def test_product_creation(self): + self.assertEqual(self.product.name, 'Laptop') + self.assertEqual(str(self.product), 'Laptop') + + def test_is_in_stock(self): + self.assertTrue(self.product.is_in_stock()) + + self.product.stock = 0 + self.assertFalse(self.product.is_in_stock()) + +# tests/test_api.py +from rest_framework.test import APITestCase, APIClient +from rest_framework import status +from django.urls import reverse + +class ProductAPITest(APITestCase): + def setUp(self): + self.client = APIClient() + self.user = User.objects.create_user( + email='test@example.com', + username='testuser', + password='testpass123' + ) + self.category = Category.objects.create(name='Electronics', slug='electronics') + + def test_create_product_authenticated(self): + self.client.force_authenticate(user=self.user) + + url = reverse('product-list') + data = { + 'name': 'New Product', + 'description': 'Product description', + 'price': 99.99, + 'stock': 50, + 'category_id': self.category.id + } + + response = self.client.post(url, data) + + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Product.objects.count(), 1) + self.assertEqual(Product.objects.first().name, 'New Product') + + def test_list_products(self): + Product.objects.create( + name='Product 1', + slug='product-1', + description='Description', + price=50.00, + stock=10, + created_by=self.user, + category=self.category + ) + + url = reverse('product-list') + response = self.client.get(url) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(len(response.data['results']), 1) +``` + +### Performance Optimization + +```python +# Use select_related and prefetch_related +# Bad +products = Product.objects.all() +for product in products: + print(product.category.name) # N+1 query problem + +# Good +products = Product.objects.select_related('category', 'created_by').all() +for product in products: + print(product.category.name) # Single query + +# For many-to-many +products = Product.objects.prefetch_related('tags').all() + +# Database indexes in models +class Meta: + indexes = [ + models.Index(fields=['slug']), + models.Index(fields=['-created_at']), + models.Index(fields=['category', '-created_at']), + ] + +# Caching with Redis +from django.core.cache import cache +from django.views.decorators.cache import cache_page + +@cache_page(60 * 15) # Cache for 15 minutes +def product_list(request): + products = Product.objects.all() + return render(request, 'products/list.html', {'products': products}) + +# Cache querysets +def get_products(): + products = cache.get('all_products') + if products is None: + products = list(Product.objects.select_related('category').all()) + cache.set('all_products', products, 60 * 15) + return products +``` + +## Critical Rules + +### Always Do +- Use environment variables for sensitive data +- Implement proper model validation +- Use select_related/prefetch_related to avoid N+1 queries +- Write tests for models, views, and APIs +- Use database indexes for frequently queried fields +- Implement proper authentication and permissions +- Document APIs with drf-spectacular +- Use Django's ORM efficiently +- Handle errors gracefully with proper HTTP status codes + +### Never Do +- Never store passwords in plain text +- Never expose Django secret key +- Never use raw SQL unless absolutely necessary +- Never skip migrations +- Never commit sensitive data +- Never trust user input without validation +- Never use DEBUG=True in production +- Never ignore database optimization + +## Knowledge Base + +- **Django Core**: Models, Views, Templates, Forms, Admin +- **Django REST Framework**: Serializers, ViewSets, Permissions, Authentication +- **ORM**: QuerySets, Managers, Aggregation, F/Q expressions +- **Authentication**: JWT, Session, OAuth +- **Testing**: Django TestCase, APITestCase +- **Performance**: Query optimization, Caching, Database indexing +- **Security**: CSRF, XSS, SQL injection prevention +- **Deployment**: WSGI, Gunicorn, Nginx, Docker + +## Integration with Other Skills + +- **Works with**: Fullstack Guardian, Test Master, DevOps Engineer +- **Complements**: FastAPI Expert (alternative Python framework), Code Documenter + +## Best Practices Summary + +1. **Models**: Use proper field types, validators, indexes +2. **Serializers**: Validate thoroughly, use nested serializers wisely +3. **ViewSets**: Use appropriate mixins, implement custom actions +4. **Permissions**: Layer permissions appropriately +5. **Testing**: High test coverage for business logic +6. **Performance**: Query optimization, caching, pagination +7. **Security**: HTTPS, CSRF tokens, proper authentication +8. **Documentation**: DRF self-documenting + drf-spectacular diff --git a/skills/fastapi-expert/SKILL.md b/skills/fastapi-expert/SKILL.md new file mode 100644 index 0000000..6e80e4c --- /dev/null +++ b/skills/fastapi-expert/SKILL.md @@ -0,0 +1,783 @@ +--- +name: FastAPI Expert +description: Expert in FastAPI framework for building high-performance async Python APIs. Use when working with FastAPI, async Python, Pydantic, type hints, automatic API documentation, WebSockets, or when the user mentions FastAPI, async APIs, or modern Python web development. +--- + +# FastAPI Expert + +A specialized skill for building high-performance, production-ready APIs with FastAPI. This skill covers async programming, Pydantic models, dependency injection, authentication, and FastAPI best practices. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify API type (REST, WebSockets, GraphQL) + - Determine database needs (async SQLAlchemy, Tortoise-ORM, MongoDB) + - Understand authentication requirements (OAuth2, JWT) + - Identify performance requirements + +2. **Project structure** + - Organize with proper router structure + - Implement dependency injection + - Use Pydantic models for validation + - Configure CORS and middleware + +3. **Implement features** + - Create path operations with proper type hints + - Implement Pydantic schemas for validation + - Add authentication and authorization + - Use background tasks for async operations + - Implement WebSockets if needed + +4. **Testing and documentation** + - Write tests using pytest and httpx + - Leverage automatic Swagger/OpenAPI docs + - Configure for production deployment + +### FastAPI Project Structure + +``` +myapp/ +├── main.py # Application entry point +├── core/ +│ ├── config.py # Settings with Pydantic +│ ├── security.py # Auth utilities +│ └── dependencies.py # Shared dependencies +├── api/ +│ ├── v1/ +│ │ ├── __init__.py +│ │ ├── api.py # Main API router +│ │ └── endpoints/ +│ │ ├── users.py +│ │ ├── auth.py +│ │ └── items.py +├── models/ # SQLAlchemy models +│ ├── __init__.py +│ ├── user.py +│ └── item.py +├── schemas/ # Pydantic schemas +│ ├── __init__.py +│ ├── user.py +│ └── item.py +├── crud/ # Database operations +│ ├── __init__.py +│ ├── user.py +│ └── item.py +├── db/ +│ ├── base.py # Database session +│ └── init_db.py # Initial data +└── tests/ + ├── conftest.py + ├── test_users.py + └── test_auth.py +``` + +### Configuration with Pydantic Settings + +```python +# core/config.py +from pydantic_settings import BaseSettings +from typing import Optional + +class Settings(BaseSettings): + # App + PROJECT_NAME: str = "My FastAPI App" + VERSION: str = "1.0.0" + API_V1_STR: str = "/api/v1" + + # Security + SECRET_KEY: str + ALGORITHM: str = "HS256" + ACCESS_TOKEN_EXPIRE_MINUTES: int = 30 + + # Database + DATABASE_URL: str + + # CORS + BACKEND_CORS_ORIGINS: list[str] = ["http://localhost:3000"] + + # Redis + REDIS_HOST: str = "localhost" + REDIS_PORT: int = 6379 + + class Config: + env_file = ".env" + case_sensitive = True + +settings = Settings() +``` + +### Pydantic Schemas + +```python +# schemas/user.py +from pydantic import BaseModel, EmailStr, Field, validator +from typing import Optional +from datetime import datetime + +class UserBase(BaseModel): + email: EmailStr + username: str = Field(..., min_length=3, max_length=50) + full_name: Optional[str] = Field(None, max_length=100) + is_active: bool = True + +class UserCreate(UserBase): + password: str = Field(..., min_length=8) + + @validator('password') + def password_strength(cls, v): + if not any(char.isdigit() for char in v): + raise ValueError('Password must contain at least one digit') + if not any(char.isupper() for char in v): + raise ValueError('Password must contain at least one uppercase letter') + return v + +class UserUpdate(BaseModel): + email: Optional[EmailStr] = None + username: Optional[str] = Field(None, min_length=3, max_length=50) + full_name: Optional[str] = Field(None, max_length=100) + password: Optional[str] = Field(None, min_length=8) + +class UserInDBBase(UserBase): + id: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True # For SQLAlchemy models + +class User(UserInDBBase): + """User response model (excludes password)""" + pass + +class UserInDB(UserInDBBase): + """User model with hashed password""" + hashed_password: str +``` + +### Database Models (SQLAlchemy) + +```python +# models/user.py +from sqlalchemy import Column, Integer, String, Boolean, DateTime +from sqlalchemy.sql import func +from db.base import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + email = Column(String, unique=True, index=True, nullable=False) + username = Column(String, unique=True, index=True, nullable=False) + full_name = Column(String) + hashed_password = Column(String, nullable=False) + is_active = Column(Boolean, default=True) + is_superuser = Column(Boolean, default=False) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + updated_at = Column(DateTime(timezone=True), onupdate=func.now()) + +# db/base.py +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker, declarative_base +from core.config import settings + +engine = create_async_engine(settings.DATABASE_URL, echo=True) +AsyncSessionLocal = sessionmaker( + engine, class_=AsyncSession, expire_on_commit=False +) + +Base = declarative_base() + +# Dependency +async def get_db(): + async with AsyncSessionLocal() as session: + try: + yield session + await session.commit() + except Exception: + await session.rollback() + raise + finally: + await session.close() +``` + +### CRUD Operations + +```python +# crud/user.py +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from sqlalchemy.exc import IntegrityError +from models.user import User +from schemas.user import UserCreate, UserUpdate +from core.security import get_password_hash +from fastapi import HTTPException, status + +async def get_user(db: AsyncSession, user_id: int) -> User | None: + result = await db.execute(select(User).where(User.id == user_id)) + return result.scalar_one_or_none() + +async def get_user_by_email(db: AsyncSession, email: str) -> User | None: + result = await db.execute(select(User).where(User.email == email)) + return result.scalar_one_or_none() + +async def get_users( + db: AsyncSession, skip: int = 0, limit: int = 100 +) -> list[User]: + result = await db.execute(select(User).offset(skip).limit(limit)) + return result.scalars().all() + +async def create_user(db: AsyncSession, user: UserCreate) -> User: + hashed_password = get_password_hash(user.password) + db_user = User( + email=user.email, + username=user.username, + full_name=user.full_name, + hashed_password=hashed_password, + is_active=user.is_active + ) + + try: + db.add(db_user) + await db.commit() + await db.refresh(db_user) + return db_user + except IntegrityError: + await db.rollback() + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="User with this email or username already exists" + ) + +async def update_user( + db: AsyncSession, user_id: int, user_update: UserUpdate +) -> User: + db_user = await get_user(db, user_id) + if not db_user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + + update_data = user_update.model_dump(exclude_unset=True) + + if "password" in update_data: + update_data["hashed_password"] = get_password_hash(update_data.pop("password")) + + for field, value in update_data.items(): + setattr(db_user, field, value) + + await db.commit() + await db.refresh(db_user) + return db_user + +async def delete_user(db: AsyncSession, user_id: int) -> bool: + db_user = await get_user(db, user_id) + if not db_user: + return False + + await db.delete(db_user) + await db.commit() + return True +``` + +### API Endpoints with Proper Type Hints + +```python +# api/v1/endpoints/users.py +from fastapi import APIRouter, Depends, HTTPException, status, Query +from sqlalchemy.ext.asyncio import AsyncSession +from typing import Annotated + +from db.base import get_db +from schemas.user import User, UserCreate, UserUpdate +from crud import user as crud_user +from core.dependencies import get_current_active_user + +router = APIRouter() + +@router.post("/", response_model=User, status_code=status.HTTP_201_CREATED) +async def create_user( + *, + db: Annotated[AsyncSession, Depends(get_db)], + user_in: UserCreate, +) -> User: + """ + Create new user. + + - **email**: valid email address (unique) + - **username**: 3-50 characters (unique) + - **password**: minimum 8 characters with uppercase, lowercase, and number + - **full_name**: optional full name + """ + # Check if user exists + existing_user = await crud_user.get_user_by_email(db, email=user_in.email) + if existing_user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="User with this email already exists", + ) + + user = await crud_user.create_user(db=db, user=user_in) + return user + +@router.get("/", response_model=list[User]) +async def read_users( + db: Annotated[AsyncSession, Depends(get_db)], + skip: int = Query(0, ge=0), + limit: int = Query(100, ge=1, le=100), + current_user: Annotated[User, Depends(get_current_active_user)] = None, +) -> list[User]: + """ + Retrieve users with pagination. + + - **skip**: number of records to skip (default: 0) + - **limit**: maximum number of records to return (default: 100, max: 100) + """ + users = await crud_user.get_users(db, skip=skip, limit=limit) + return users + +@router.get("/{user_id}", response_model=User) +async def read_user( + user_id: int, + db: Annotated[AsyncSession, Depends(get_db)], +) -> User: + """ + Get user by ID. + """ + db_user = await crud_user.get_user(db, user_id=user_id) + if db_user is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) + return db_user + +@router.patch("/{user_id}", response_model=User) +async def update_user( + user_id: int, + user_in: UserUpdate, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)] = None, +) -> User: + """ + Update user. + """ + # Check authorization (users can only update themselves unless admin) + if current_user.id != user_id and not current_user.is_superuser: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not enough permissions" + ) + + db_user = await crud_user.update_user(db, user_id=user_id, user_update=user_in) + return db_user + +@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_user( + user_id: int, + db: Annotated[AsyncSession, Depends(get_db)], + current_user: Annotated[User, Depends(get_current_active_user)] = None, +) -> None: + """ + Delete user. + """ + if not current_user.is_superuser: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Not enough permissions" + ) + + success = await crud_user.delete_user(db, user_id=user_id) + if not success: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="User not found" + ) +``` + +### Authentication with JWT + +```python +# core/security.py +from datetime import datetime, timedelta +from typing import Optional +from jose import JWTError, jwt +from passlib.context import CryptContext +from core.config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str: + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + +# core/dependencies.py +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import JWTError, jwt +from sqlalchemy.ext.asyncio import AsyncSession + +from core.config import settings +from core.security import verify_password +from db.base import get_db +from crud import user as crud_user +from schemas.user import User + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login") + +async def get_current_user( + db: AsyncSession = Depends(get_db), + token: str = Depends(oauth2_scheme) +) -> User: + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + + try: + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) + user_id: int = payload.get("sub") + if user_id is None: + raise credentials_exception + except JWTError: + raise credentials_exception + + user = await crud_user.get_user(db, user_id=user_id) + if user is None: + raise credentials_exception + + return user + +async def get_current_active_user( + current_user: User = Depends(get_current_user) +) -> User: + if not current_user.is_active: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Inactive user" + ) + return current_user + +# api/v1/endpoints/auth.py +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.ext.asyncio import AsyncSession +from datetime import timedelta + +from db.base import get_db +from core.config import settings +from core.security import create_access_token, verify_password +from crud import user as crud_user +from schemas.user import User + +router = APIRouter() + +@router.post("/login") +async def login( + db: AsyncSession = Depends(get_db), + form_data: OAuth2PasswordRequestForm = Depends() +): + """ + OAuth2 compatible token login. + """ + user = await crud_user.get_user_by_email(db, email=form_data.username) + + if not user or not verify_password(form_data.password, user.hashed_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + + if not user.is_active: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Inactive user" + ) + + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": str(user.id)}, expires_delta=access_token_expires + ) + + return { + "access_token": access_token, + "token_type": "bearer", + "user": User.model_validate(user) + } + +@router.get("/me", response_model=User) +async def read_users_me( + current_user: User = Depends(get_current_active_user) +): + """ + Get current user. + """ + return current_user +``` + +### Background Tasks + +```python +from fastapi import BackgroundTasks +from typing import Annotated + +def send_email(email: str, message: str): + """Simulated email sending""" + print(f"Sending email to {email}: {message}") + # Actual email sending logic here + +@router.post("/send-notification/") +async def send_notification( + email: str, + background_tasks: BackgroundTasks +): + background_tasks.add_task(send_email, email, "Welcome to our service!") + return {"message": "Notification sent in the background"} +``` + +### WebSocket Support + +```python +from fastapi import WebSocket, WebSocketDisconnect + +class ConnectionManager: + def __init__(self): + self.active_connections: list[WebSocket] = [] + + async def connect(self, websocket: WebSocket): + await websocket.accept() + self.active_connections.append(websocket) + + def disconnect(self, websocket: WebSocket): + self.active_connections.remove(websocket) + + async def broadcast(self, message: str): + for connection in self.active_connections: + await connection.send_text(message) + +manager = ConnectionManager() + +@router.websocket("/ws/{client_id}") +async def websocket_endpoint(websocket: WebSocket, client_id: int): + await manager.connect(websocket) + try: + while True: + data = await websocket.receive_text() + await manager.broadcast(f"Client #{client_id} says: {data}") + except WebSocketDisconnect: + manager.disconnect(websocket) + await manager.broadcast(f"Client #{client_id} left the chat") +``` + +### Main Application Setup + +```python +# main.py +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from contextlib import asynccontextmanager + +from core.config import settings +from api.v1.api import api_router +from db.base import engine, Base + +@asynccontextmanager +async def lifespan(app: FastAPI): + # Startup + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + yield + # Shutdown + await engine.dispose() + +app = FastAPI( + title=settings.PROJECT_NAME, + version=settings.VERSION, + openapi_url=f"{settings.API_V1_STR}/openapi.json", + lifespan=lifespan +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=settings.BACKEND_CORS_ORIGINS, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +app.include_router(api_router, prefix=settings.API_V1_STR) + +@app.get("/") +async def root(): + return {"message": "Welcome to FastAPI"} + +@app.get("/health") +async def health_check(): + return {"status": "healthy"} +``` + +### Testing + +```python +# tests/conftest.py +import pytest +import pytest_asyncio +from httpx import AsyncClient +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker + +from main import app +from db.base import Base, get_db +from core.config import settings + +TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:" + +@pytest_asyncio.fixture +async def db_session(): + engine = create_async_engine(TEST_DATABASE_URL, echo=True) + async_session = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + async with async_session() as session: + yield session + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + + await engine.dispose() + +@pytest_asyncio.fixture +async def client(db_session): + async def override_get_db(): + yield db_session + + app.dependency_overrides[get_db] = override_get_db + + async with AsyncClient(app=app, base_url="http://test") as ac: + yield ac + + app.dependency_overrides.clear() + +# tests/test_users.py +import pytest +from httpx import AsyncClient + +@pytest.mark.asyncio +async def test_create_user(client: AsyncClient): + response = await client.post( + "/api/v1/users/", + json={ + "email": "test@example.com", + "username": "testuser", + "password": "Test123456", + "full_name": "Test User" + } + ) + + assert response.status_code == 201 + data = response.json() + assert data["email"] == "test@example.com" + assert data["username"] == "testuser" + assert "id" in data + assert "hashed_password" not in data + +@pytest.mark.asyncio +async def test_read_users(client: AsyncClient): + # Create a user first + await client.post( + "/api/v1/users/", + json={ + "email": "test@example.com", + "username": "testuser", + "password": "Test123456" + } + ) + + # Login to get token + response = await client.post( + "/api/v1/auth/login", + data={"username": "test@example.com", "password": "Test123456"} + ) + token = response.json()["access_token"] + + # Get users + response = await client.get( + "/api/v1/users/", + headers={"Authorization": f"Bearer {token}"} + ) + + assert response.status_code == 200 + assert len(response.json()) > 0 +``` + +## Critical Rules + +### Always Do +- Use type hints everywhere (FastAPI requires them) +- Leverage Pydantic for validation and serialization +- Use async/await for all I/O operations +- Implement proper dependency injection +- Use background tasks for long-running operations +- Document all endpoints (FastAPI auto-generates OpenAPI) +- Handle errors with proper HTTP status codes +- Use environment variables for configuration +- Implement proper authentication and authorization + +### Never Do +- Never use synchronous database operations +- Never skip type hints (FastAPI won't work properly) +- Never store passwords in plain text +- Never expose sensitive data in responses +- Never ignore Pydantic validation errors +- Never mix sync and async code improperly +- Never hardcode configuration values +- Never skip error handling + +## Knowledge Base + +- **FastAPI Core**: Path operations, dependencies, middleware, background tasks +- **Async Python**: asyncio, async/await, async database drivers +- **Pydantic**: Models, validation, serialization, settings management +- **SQLAlchemy**: Async ORM, relationships, migrations with Alembic +- **Authentication**: OAuth2, JWT, password hashing +- **Testing**: pytest, pytest-asyncio, httpx +- **Performance**: Async operations, caching, connection pooling +- **Documentation**: Automatic OpenAPI/Swagger generation + +## Integration with Other Skills + +- **Works with**: Fullstack Guardian, Test Master, DevOps Engineer +- **Complements**: Django Expert (alternative Python framework), Code Documenter + +## Best Practices Summary + +1. **Type Hints**: Essential for FastAPI functionality +2. **Async Everything**: Use async for I/O operations +3. **Pydantic Models**: Validate all input/output +4. **Dependency Injection**: Use FastAPI's DI system +5. **Auto Documentation**: Leverage automatic OpenAPI docs +6. **Error Handling**: Proper HTTP exceptions +7. **Security**: JWT authentication, password hashing +8. **Testing**: Comprehensive async tests +9. **Performance**: Background tasks, caching, connection pooling +10. **Configuration**: Pydantic Settings for environment management diff --git a/skills/feature-forge/SKILL.md b/skills/feature-forge/SKILL.md new file mode 100644 index 0000000..39c4337 --- /dev/null +++ b/skills/feature-forge/SKILL.md @@ -0,0 +1,163 @@ +--- +name: Feature Forge +description: Gather requirements and define new features through a structured workshop process. Use when the user wants to define a new feature, create specifications, gather requirements, write user stories, or mentions needing to plan a new capability or functionality. +--- + +# Feature Forge + +A specialized skill that simulates a requirements-gathering workshop. This skill embodies two distinct personas: + +- **Product Manager (PM Hat)**: Focused on user value, business goals, and the "why" behind the feature +- **Senior Developer (Dev Hat)**: Focused on technical feasibility, security, scalability, non-functional requirements, and the "how" + +## Instructions + +### Core Workflow + +1. **Start with the basics** + - Ask for the name of the feature + - Gather the high-level goal and user value proposition + +2. **Conduct a methodical interview** + - Lead an interrogatory interview process to gather comprehensive requirements + - Follow a logical flow: from high-level user goals to detailed technical constraints + - Clearly indicate which persona is speaking using `[PM Hat]` or `[Dev Hat]` + +3. **Ask clarifying questions** + - Resolve all ambiguity before finalizing the specification + - Challenge assumptions to ensure robustness + - If the user says "fill in the blanks" or "suggest a best practice", provide well-reasoned suggestions based on: + - UX principles (for PM perspective) + - Engineering/security best practices (for Dev perspective) + - Always label suggestions clearly + +4. **Use EARS format for requirements** + - Write all functional requirements in **EARS (Easy Approach to Requirements Syntax)** + - Format: "While ``, when ``, the system shall ``" + - Example: "While ``, when ``, the system shall ``" + +5. **Generate comprehensive specification** + - Create a complete specification document in markdown + - Name it `specs/{name_of_feature}.spec.md` + - Include all required sections (see structure below) + +### Interview Flow + +#### PM Hat Questions +- What problem does this feature solve? +- Who are the target users? +- What are the key user workflows? +- What success metrics will we track? +- What are the business constraints? + +#### Dev Hat Questions +- What are the technical constraints? +- What existing systems does this integrate with? +- What are the performance requirements? +- What are the security considerations? +- What are the scalability requirements? +- How should errors be handled? +- What data needs to be persisted? + +### Specification Structure + +The final specification must contain these exact headings in this order: + +1. **Functional Requirements** + - All requirements in EARS format + - Cover all user-facing functionality + - Include triggers, preconditions, and system responses + +2. **Non-Functional Requirements** + - Performance requirements (response times, throughput) + - Security requirements (authentication, authorization, data protection) + - Scalability requirements (concurrent users, data volume) + - Availability requirements (uptime, disaster recovery) + - Usability requirements (accessibility, responsiveness) + +3. **Acceptance Criteria** + - Clear, testable criteria for feature completion + - User-focused validation points + - Technical validation points + +4. **Testing Strategy** + - Unit testing approach + - Integration testing approach + - End-to-end testing approach + - Performance testing approach + - Security testing approach + +5. **TODO** + - Checklist of small, logical, sequential implementation steps + - Intended to guide the Fullstack Guardian during implementation + - Broken down by component/layer where appropriate + +## Critical Rules + +### Always Do +- Conduct a thorough interview before generating the final specification +- Ask for clarification on vague requirements +- Consider security, performance, and error handling +- Write requirements in EARS format +- Include a detailed implementation TODO checklist + +### Never Do +- Never generate the final spec without conducting a thorough interview +- Never accept vague requirements without asking for clarification +- Never forget to consider security, performance, and error handling +- Never skip the TODO section + +## Knowledge Base + +- **EARS Format**: Expert in writing requirements using the EARS syntax (Easy Approach to Requirements Syntax) +- **UX Design**: Knowledgeable in modern UX design principles and best practices +- **Secure Coding**: Familiar with OWASP Top 10 and secure coding practices +- **System Design**: Understands principles for building scalable and reliable software +- **Best Practices**: Knowledgeable in modern software development methodologies + +## Integration with Other Skills + +- **Outputs to**: Fullstack Guardian (for implementation), Test Master (for test planning) +- **Can receive from**: Spec Miner (for enhancement of existing features) +- **Collaborates with**: All development personas during requirements refinement + +## Examples + +### Example 1: User Authentication Feature +``` +[PM Hat] Let's define the user authentication feature. Can you tell me: +1. What types of authentication do you want to support? (email/password, OAuth, SSO?) +2. What should happen when a user forgets their password? +3. Are there any specific compliance requirements (GDPR, HIPAA, etc.)? + +[Dev Hat] From a technical perspective, I need to understand: +1. What password hashing algorithm should we use? (bcrypt, argon2?) +2. Do we need multi-factor authentication? +3. What session management approach? (JWT, server-side sessions?) +4. How long should sessions last? +5. What rate limiting should be in place for login attempts? +``` + +### Example 2: Data Export Feature +``` +[PM Hat] For the data export feature: +1. What formats should users be able to export to? (CSV, Excel, PDF?) +2. Should there be limits on export size? +3. How should users access their exported files? + +[Dev Hat] Technical considerations: +1. Should large exports be processed asynchronously? +2. What data should be included/excluded for privacy? +3. How long should exported files be retained? +4. Should we implement pagination for large datasets? +``` + +## Best Practices + +1. **Be Thorough**: Don't rush the requirements gathering process +2. **Think Security First**: Always consider security implications from the start +3. **User-Centered**: Keep the user's needs and experience at the forefront +4. **Technical Feasibility**: Balance ideal solutions with practical implementation +5. **Clear Communication**: Use precise language and avoid ambiguity +6. **Iterative Refinement**: Be willing to revisit and refine requirements +7. **Documentation**: Create specifications that serve as implementation blueprints diff --git a/skills/flutter-expert/SKILL.md b/skills/flutter-expert/SKILL.md new file mode 100644 index 0000000..e094faa --- /dev/null +++ b/skills/flutter-expert/SKILL.md @@ -0,0 +1,429 @@ +--- +name: Flutter Expert +description: Expert in Flutter for building cross-platform applications. Use when working with Flutter, Dart, widgets, state management (Provider, Riverpod, Bloc), Material Design, Cupertino, or when the user mentions Flutter, Dart, mobile development, or cross-platform apps. +--- + +# Flutter Expert + +A specialized skill for building high-performance cross-platform applications with Flutter and Dart. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify platforms (iOS, Android, Web, Desktop) + - Determine state management approach + - Understand UI/UX requirements + - Identify native integrations needed + +2. **Project structure** + - Organize with feature-based architecture + - Implement proper state management + - Set up routing and navigation + - Configure for multiple platforms + +3. **Implement features** + - Create reusable widgets + - Implement responsive layouts + - Handle platform-specific code + - Optimize performance + +4. **Testing and deployment** + - Write widget, integration, and unit tests + - Optimize app size and performance + - Configure platform-specific builds + +### Flutter Project Structure + +``` +lib/ +├── main.dart +├── app.dart +├── core/ +│ ├── constants/ +│ ├── themes/ +│ ├── utils/ +│ └── extensions/ +├── features/ +│ ├── auth/ +│ │ ├── data/ +│ │ ├── domain/ +│ │ ├── presentation/ +│ │ └── providers/ +│ └── home/ +├── shared/ +│ ├── widgets/ +│ ├── models/ +│ └── services/ +└── routes/ +``` + +### Widget Best Practices + +```dart +import 'package:flutter/material.dart'; + +// Stateless Widget +class CustomButton extends StatelessWidget { + final String label; + final VoidCallback onPressed; + final bool isLoading; + + const CustomButton({ + Key? key, + required this.label, + required this.onPressed, + this.isLoading = false, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: isLoading ? null : onPressed, + child: isLoading + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : Text(label), + ); + } +} + +// Stateful Widget +class Counter extends StatefulWidget { + const Counter({Key? key}) : super(key: key); + + @override + State createState() => _CounterState(); +} + +class _CounterState extends State { + int _count = 0; + + void _increment() { + setState(() { + _count++; + }); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Text('Count: $_count'), + ElevatedButton( + onPressed: _increment, + child: const Text('Increment'), + ), + ], + ); + } +} +``` + +### State Management (Riverpod) + +```dart +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +// Provider +final counterProvider = StateProvider((ref) => 0); + +// FutureProvider +final userProvider = FutureProvider((ref) async { + final response = await http.get(Uri.parse('/api/user')); + return User.fromJson(jsonDecode(response.body)); +}); + +// StateNotifier +class TodosNotifier extends StateNotifier> { + TodosNotifier() : super([]); + + void addTodo(Todo todo) { + state = [...state, todo]; + } + + void removeTodo(String id) { + state = state.where((todo) => todo.id != id).toList(); + } + + void toggleTodo(String id) { + state = [ + for (final todo in state) + if (todo.id == id) + todo.copyWith(completed: !todo.completed) + else + todo, + ]; + } +} + +final todosProvider = StateNotifierProvider>((ref) { + return TodosNotifier(); +}); + +// Using in Widget +class TodoList extends ConsumerWidget { + const TodoList({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final todos = ref.watch(todosProvider); + + return ListView.builder( + itemCount: todos.length, + itemBuilder: (context, index) { + final todo = todos[index]; + return ListTile( + title: Text(todo.title), + leading: Checkbox( + value: todo.completed, + onChanged: (_) { + ref.read(todosProvider.notifier).toggleTodo(todo.id); + }, + ), + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + ref.read(todosProvider.notifier).removeTodo(todo.id); + }, + ), + ); + }, + ); + } +} +``` + +### Responsive Design + +```dart +class ResponsiveLayout extends StatelessWidget { + final Widget mobile; + final Widget? tablet; + final Widget desktop; + + const ResponsiveLayout({ + Key? key, + required this.mobile, + this.tablet, + required this.desktop, + }) : super(key: key); + + static bool isMobile(BuildContext context) => + MediaQuery.of(context).size.width < 650; + + static bool isTablet(BuildContext context) => + MediaQuery.of(context).size.width >= 650 && + MediaQuery.of(context).size.width < 1100; + + static bool isDesktop(BuildContext context) => + MediaQuery.of(context).size.width >= 1100; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + if (constraints.maxWidth >= 1100) { + return desktop; + } else if (constraints.maxWidth >= 650) { + return tablet ?? mobile; + } else { + return mobile; + } + }, + ); + } +} +``` + +### Navigation (GoRouter) + +```dart +import 'package:go_router/go_router.dart'; + +final goRouter = GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + builder: (context, state) => const HomeScreen(), + ), + GoRoute( + path: '/details/:id', + builder: (context, state) { + final id = state.pathParameters['id']!; + return DetailsScreen(id: id); + }, + ), + GoRoute( + path: '/profile', + builder: (context, state) => const ProfileScreen(), + ), + ], + redirect: (context, state) { + // Add auth guard logic here + final isLoggedIn = false; // Check auth state + if (!isLoggedIn && state.location != '/login') { + return '/login'; + } + return null; + }, +); + +// In main.dart +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp.router( + routerConfig: goRouter, + theme: ThemeData(primarySwatch: Colors.blue), + ); + } +} + +// Navigate +context.go('/details/123'); +context.push('/profile'); +``` + +### API Integration + +```dart +import 'package:http/http.dart' as http; +import 'dart:convert'; + +class ApiService { + static const baseUrl = 'https://api.example.com'; + + Future> getUsers() async { + final response = await http.get(Uri.parse('$baseUrl/users')); + + if (response.statusCode == 200) { + final List data = jsonDecode(response.body); + return data.map((json) => User.fromJson(json)).toList(); + } else { + throw Exception('Failed to load users'); + } + } + + Future createUser(User user) async { + final response = await http.post( + Uri.parse('$baseUrl/users'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(user.toJson()), + ); + + if (response.statusCode == 201) { + return User.fromJson(jsonDecode(response.body)); + } else { + throw Exception('Failed to create user'); + } + } +} + +// With Riverpod +final apiServiceProvider = Provider((ref) => ApiService()); + +final usersProvider = FutureProvider>((ref) async { + final apiService = ref.read(apiServiceProvider); + return apiService.getUsers(); +}); +``` + +### Testing + +```dart +// Widget Test +void main() { + testWidgets('Counter increments', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp(home: Counter())); + + expect(find.text('Count: 0'), findsOneWidget); + + await tester.tap(find.text('Increment')); + await tester.pump(); + + expect(find.text('Count: 1'), findsOneWidget); + }); + + // Unit Test + test('TodosNotifier adds todo', () { + final notifier = TodosNotifier(); + final todo = Todo(id: '1', title: 'Test', completed: false); + + notifier.addTodo(todo); + + expect(notifier.state.length, 1); + expect(notifier.state.first.title, 'Test'); + }); + + // Integration Test + testWidgets('Full app test', (WidgetTester tester) async { + await tester.pumpWidget(MyApp()); + + expect(find.text('Home'), findsOneWidget); + + await tester.tap(find.text('Go to Profile')); + await tester.pumpAndSettle(); + + expect(find.text('Profile'), findsOneWidget); + }); +} +``` + +## Critical Rules + +### Always Do +- Use const constructors when possible +- Implement proper keys for lists +- Handle async operations properly +- Use BuildContext appropriately +- Implement error handling +- Test on all target platforms +- Optimize widget rebuilds +- Use proper state management +- Follow Material/Cupertino guidelines +- Profile performance + +### Never Do +- Never build widgets in build method +- Never mutate state directly +- Never ignore platform differences +- Never skip const constructors +- Never create unnecessary StatefulWidgets +- Never ignore memory leaks +- Never block the UI thread +- Never hardcode sizes + +## Knowledge Base + +- **Flutter Core**: Widgets, State Management, Navigation +- **Dart**: Language features, async/await, null safety +- **State Management**: Provider, Riverpod, Bloc, GetX +- **UI**: Material Design, Cupertino, Custom painters +- **Platform**: iOS, Android, Web, Desktop +- **Testing**: Widget tests, Integration tests, Unit tests +- **Performance**: Profiling, optimization techniques +- **Native**: Platform channels, method channels + +## Integration with Other Skills + +- **Works with**: Fullstack Guardian, Test Master +- **Complements**: React Native Expert (alternative mobile) + +## Best Practices Summary + +1. **Widgets**: Use const, proper keys, avoid rebuilds +2. **State**: Choose appropriate state management +3. **Performance**: Profile and optimize +4. **Platform**: Handle platform differences +5. **Testing**: Comprehensive test coverage +6. **Navigation**: Type-safe routing +7. **API**: Proper error handling +8. **UI**: Follow platform guidelines +9. **Code**: Clean, maintainable, documented +10. **Build**: Optimize app size diff --git a/skills/fullstack-guardian/SKILL.md b/skills/fullstack-guardian/SKILL.md new file mode 100644 index 0000000..3cd8144 --- /dev/null +++ b/skills/fullstack-guardian/SKILL.md @@ -0,0 +1,236 @@ +--- +name: Fullstack Guardian +description: Implement secure, scalable full-stack features across frontend, backend, and security layers. Use when implementing new features, writing code across the stack, building APIs, creating UIs, ensuring security, or when the user mentions full-stack development, implementation, or coding tasks. +--- + +# Fullstack Guardian + +A specialized skill that simulates a highly competent, security-focused full-stack developer. This skill embodies three distinct personas: + +- **Frontend Developer (Frontend Hat)**: Focused on user experience, accessibility, client-side performance, and responsive design +- **Backend Developer (Backend Hat)**: Focused on data modeling, API design, business logic, database interactions, and server-side performance +- **Security Engineer (Security Hat)**: Focused on identifying and mitigating vulnerabilities, ensuring data protection, and adhering to secure coding standards + +## Instructions + +### Core Workflow + +1. **Gather requirements** + - Ask for the name of the feature and its high-level goal + - Request or review the specification (typically from Feature Forge) + - Clarify any ambiguous requirements + +2. **Design the solution** + - Lead a structured discussion covering all three perspectives + - Clearly indicate which persona is speaking using `[Frontend Hat]`, `[Backend Hat]`, or `[Security Hat]` + - Follow logical design flow: user interaction → data flow → security integration + +3. **Ask clarifying questions** + - Resolve ambiguity, especially for cross-cutting concerns + - If the user says "suggest a best practice" or "recommend an approach", provide well-reasoned suggestions based on: + - Modern full-stack development patterns + - Secure coding principles + - Architectural best practices + - Always label suggestions clearly + +4. **Use EARS format for requirements** + - Write all functional requirements in **EARS (Easy Approach to Requirements Syntax)** + - Format: "While ``, when ``, the system shall ``" + - Example: "While ``, when ``, the system shall ``" + +5. **Generate technical design document** + - Create comprehensive technical design in markdown + - Name it `specs/{name_of_feature}_fullstack_design.md` + - Include all required sections (see structure below) + +6. **Implement the feature** + - Execute the implementation plan step by step + - Use Write, Edit, and Bash tools as needed + - Update the implementation plan as tasks are completed + - Test implementation as you go + +7. **Hand off to testing and deployment** + - Pass completed feature to Test Master for QA + - Pass tested feature to DevOps Engineer for deployment + +### Design Flow + +#### Frontend Hat Considerations +- User interface design and component structure +- Client-side state management +- Form validation and user input handling +- Accessibility (WCAG compliance) +- Responsive design for multiple devices +- Client-side performance optimization +- Error messaging and user feedback + +#### Backend Hat Considerations +- API design (REST, GraphQL, etc.) +- Data modeling and database schema +- Business logic and validation +- Server-side performance optimization +- Caching strategies +- Error handling and logging +- Integration with external services + +#### Security Hat Considerations +- Authentication and authorization +- Input validation and sanitization +- SQL injection prevention +- XSS (Cross-Site Scripting) prevention +- CSRF (Cross-Site Request Forgery) protection +- Secure data storage (encryption at rest) +- Secure data transmission (TLS/SSL) +- Rate limiting and DDoS protection +- Security headers +- Sensitive data handling (PII, credentials) + +### Technical Design Document Structure + +The document must contain these exact headings in this order: + +1. **Functional Requirements** + - All requirements in EARS format + - Complete coverage of functionality + +2. **Non-Functional Requirements (Security, Performance, Scalability)** + - Security requirements and threat model + - Performance targets (response times, throughput) + - Scalability requirements (concurrent users, data growth) + - Availability and reliability requirements + +3. **Frontend Considerations** + - UI/UX design approach + - Component architecture + - State management strategy + - Client-side validation + - Accessibility considerations + +4. **Backend Considerations** + - API design and endpoints + - Data model and schema + - Business logic flow + - Database queries and optimization + - Caching strategy + +5. **Security Considerations** + - Authentication/authorization approach + - Input validation strategy + - Data protection measures + - Common vulnerability mitigations (OWASP Top 10) + - Security testing approach + +6. **Acceptance Criteria** + - Functional validation points + - Non-functional validation points + - Security validation points + +7. **Testing Strategy** + - Unit testing approach + - Integration testing approach + - End-to-end testing approach + - Security testing approach + - Performance testing approach + +8. **Implementation Plan** + - Checklist of small, logical, sequential steps + - Broken down by frontend, backend, and security tasks + - Each task should be independently testable + - Update as implementation progresses + +## Critical Rules + +### Always Do +- Cover all three perspectives (frontend, backend, security) in the design +- Ask for clarification on vague requirements +- Explicitly consider security, performance, error handling, and scalability at each stage +- Use available tools (Write, Edit, Read, Bash) for implementation +- Mark implementation tasks as complete as they are finished +- Hand off completed features to Test Master and DevOps Engineer + +### Never Do +- Never generate the final spec without thorough discussion covering all three perspectives +- Never accept vague requirements without asking for clarification +- Never forget security considerations +- Never skip error handling +- Never implement without considering scalability and performance + +## Knowledge Base + +- **EARS Format**: Expert in writing requirements using EARS syntax +- **Frontend Frameworks**: Knowledgeable in React, Vue, Angular, and modern frontend development +- **Backend Architectures**: Familiar with REST, GraphQL, microservices, and monolithic patterns +- **Databases**: Understanding of SQL (PostgreSQL, MySQL) and NoSQL (MongoDB, Redis) databases +- **API Development**: Expert in RESTful API design, GraphQL, and API security +- **Secure Coding**: Expert in OWASP Top 10, threat modeling, and secure design principles +- **Testing**: Understanding of unit, integration, E2E, performance, and security testing + +## Integration with Other Skills + +- **Receives from**: Feature Forge (specifications) +- **Outputs to**: Test Master (for testing), DevOps Engineer (for deployment) +- **Collaborates with**: All personas for comprehensive feature development + +## Examples + +### Example 1: User Profile Update Feature + +``` +[Frontend Hat] For the user profile update feature, we'll need: +1. A form component with fields for name, email, bio, and avatar +2. Client-side validation for email format and required fields +3. Real-time validation feedback to users +4. Loading states during submission +5. Success/error messages + +[Backend Hat] On the backend, we'll implement: +1. PUT /api/users/:id endpoint +2. Request validation middleware +3. Database update with transaction support +4. Email verification if email changes +5. Audit logging for profile changes + +[Security Hat] Security measures required: +1. Verify user can only update their own profile (authorization) +2. Sanitize all input to prevent XSS +3. Rate limit the endpoint to prevent abuse +4. Validate file uploads (avatar) for size and type +5. Check for suspicious activity (multiple rapid updates) +``` + +### Example 2: Search Feature with Filters + +``` +[Frontend Hat] Search interface needs: +1. Search input with debouncing (300ms) +2. Filter dropdowns for category, date range, status +3. Results list with pagination +4. Loading skeleton while fetching +5. Empty state when no results + +[Backend Hat] Backend implementation: +1. GET /api/search endpoint with query parameters +2. Database query optimization with indexes +3. Pagination support (limit/offset or cursor-based) +4. Caching of popular searches (Redis) +5. Search analytics logging + +[Security Hat] Security considerations: +1. Input sanitization to prevent SQL injection +2. Rate limiting to prevent search abuse +3. Access control (only show results user can access) +4. Protect against information disclosure through search +``` + +## Best Practices + +1. **Security First**: Consider security implications at every layer +2. **Performance Matters**: Optimize for speed without sacrificing security +3. **Error Handling**: Plan for failure at every step +4. **Testing**: Write testable code with good separation of concerns +5. **Documentation**: Document complex logic and security decisions +6. **Scalability**: Design for growth from the start +7. **Accessibility**: Make features usable for everyone +8. **Code Quality**: Write clean, maintainable, well-structured code +9. **Incremental Progress**: Break work into small, verifiable steps +10. **Communication**: Keep stakeholders informed of progress and blockers diff --git a/skills/monitoring-expert/SKILL.md b/skills/monitoring-expert/SKILL.md new file mode 100644 index 0000000..796db7d --- /dev/null +++ b/skills/monitoring-expert/SKILL.md @@ -0,0 +1,258 @@ +--- +name: Monitoring Expert +description: Expert in application monitoring, observability, logging, metrics, and alerting. Use when setting up monitoring, observability, logging, metrics, tracing, alerting, APM, or when the user mentions monitoring, observability, Prometheus, Grafana, ELK, DataDog, or New Relic. +--- + +# Monitoring Expert + +Expert in building comprehensive monitoring and observability solutions for production systems. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify what needs monitoring + - Determine SLIs/SLOs/SLAs + - Understand alert requirements + - Identify stakeholders + +2. **Implement Three Pillars of Observability** + - **Logs**: Structured logging + - **Metrics**: Time-series data + - **Traces**: Distributed tracing + +3. **Set up monitoring stack** + - Choose tools (Prometheus, Grafana, ELK, DataDog, etc.) + - Implement instrumentation + - Configure dashboards + - Set up alerts + +4. **Define SLIs/SLOs** + - Service Level Indicators (what to measure) + - Service Level Objectives (targets) + - Error budgets + - Alerting thresholds + +### Logging Best Practices + +```typescript +// Structured logging (JSON) +import winston from 'winston'; + +const logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + defaultMeta: { service: 'user-service' }, + transports: [ + new winston.transports.File({ filename: 'error.log', level: 'error' }), + new winston.transports.File({ filename: 'combined.log' }), + ], +}); + +logger.info('User logged in', { + userId: user.id, + email: user.email, + ip: req.ip, +}); + +logger.error('Database connection failed', { + error: error.message, + stack: error.stack, + database: config.database, +}); +``` + +### Metrics (Prometheus) + +```typescript +import { Registry, Counter, Histogram, Gauge } from 'prom-client'; + +const register = new Registry(); + +// Counter - monotonically increasing +const httpRequestsTotal = new Counter({ + name: 'http_requests_total', + help: 'Total number of HTTP requests', + labelNames: ['method', 'route', 'status_code'], + registers: [register], +}); + +// Histogram - distribution of values +const httpRequestDuration = new Histogram({ + name: 'http_request_duration_seconds', + help: 'Duration of HTTP requests in seconds', + labelNames: ['method', 'route', 'status_code'], + buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10], + registers: [register], +}); + +// Gauge - value that can go up or down +const activeConnections = new Gauge({ + name: 'active_connections', + help: 'Number of active connections', + registers: [register], +}); + +// Middleware to track metrics +app.use((req, res, next) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = (Date.now() - start) / 1000; + + httpRequestsTotal.labels(req.method, req.route.path, res.statusCode).inc(); + httpRequestDuration.labels(req.method, req.route.path, res.statusCode).observe(duration); + }); + + next(); +}); + +// Metrics endpoint +app.get('/metrics', async (req, res) => { + res.set('Content-Type', register.contentType); + res.end(await register.metrics()); +}); +``` + +### Distributed Tracing (OpenTelemetry) + +```typescript +import { NodeSDK } from '@opentelemetry/sdk-node'; +import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; +import { Resource } from '@opentelemetry/resources'; +import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; + +const sdk = new NodeSDK({ + resource: new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'my-service', + [SemanticResourceAttributes.SERVICE_VERSION]: '1.0.0', + }), + instrumentations: [getNodeAutoInstrumentations()], +}); + +sdk.start(); +``` + +### Alerting Rules (Prometheus) + +```yaml +groups: + - name: api_alerts + rules: + - alert: HighErrorRate + expr: rate(http_requests_total{status_code=~"5.."}[5m]) > 0.05 + for: 5m + labels: + severity: critical + annotations: + summary: "High error rate detected" + description: "Error rate is {{ $value }} errors/sec" + + - alert: HighLatency + expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 1 + for: 5m + labels: + severity: warning + annotations: + summary: "High latency detected" + description: "95th percentile latency is {{ $value }}s" + + - alert: ServiceDown + expr: up{job="api"} == 0 + for: 1m + labels: + severity: critical + annotations: + summary: "Service is down" +``` + +### Health Checks + +```typescript +app.get('/health', async (req, res) => { + const checks = { + uptime: process.uptime(), + timestamp: Date.now(), + status: 'ok', + checks: { + database: await checkDatabase(), + redis: await checkRedis(), + externalApi: await checkExternalApi(), + }, + }; + + const isHealthy = Object.values(checks.checks).every(check => check.status === 'ok'); + + res.status(isHealthy ? 200 : 503).json(checks); +}); + +async function checkDatabase() { + try { + await db.query('SELECT 1'); + return { status: 'ok' }; + } catch (error) { + return { status: 'error', message: error.message }; + } +} +``` + +### Dashboards (Grafana) + +Key metrics to display: +- **RED metrics** (Rate, Errors, Duration) + - Request rate + - Error rate + - Request duration (latency) +- **USE metrics** (Utilization, Saturation, Errors) + - CPU/Memory utilization + - Queue depth + - Error counts +- **Business metrics** + - Active users + - Transactions per second + - Revenue metrics + +## Critical Rules + +### Always Do +- Implement structured logging +- Define SLIs/SLOs +- Set up meaningful alerts +- Monitor the four golden signals (latency, traffic, errors, saturation) +- Include context in logs (request ID, user ID, etc.) +- Use log levels appropriately +- Monitor dependencies +- Set up dashboards +- Document runbooks for alerts + +### Never Do +- Never log sensitive data (passwords, tokens, PII) +- Never alert on everything (alert fatigue) +- Never ignore monitoring in development +- Never skip health checks +- Never hardcode thresholds without reasoning +- Never collect metrics without using them + +## Knowledge Base + +- **Tools**: Prometheus, Grafana, ELK Stack, DataDog, New Relic, Sentry +- **Concepts**: SLIs, SLOs, SLAs, Error budgets +- **Patterns**: RED metrics, USE metrics, Four golden signals +- **Protocols**: OpenTelemetry, StatsD, Syslog + +## Best Practices Summary + +1. **Three Pillars**: Logs, Metrics, Traces +2. **Structured Logging**: JSON format with context +3. **Meaningful Metrics**: Track what matters +4. **Smart Alerts**: Actionable, not noisy +5. **Dashboards**: Clear, focused, role-specific +6. **SLOs**: Define and track service objectives +7. **Health Checks**: Comprehensive dependency checks +8. **Documentation**: Runbooks for all alerts +9. **Testing**: Test monitoring and alerts +10. **Privacy**: Never log sensitive data diff --git a/skills/nestjs-expert/SKILL.md b/skills/nestjs-expert/SKILL.md new file mode 100644 index 0000000..1b064ce --- /dev/null +++ b/skills/nestjs-expert/SKILL.md @@ -0,0 +1,666 @@ +--- +name: NestJS Expert +description: Expert in NestJS framework for building scalable Node.js server-side applications. Use when working with NestJS, TypeScript backend development, dependency injection, decorators, modules, controllers, providers, guards, interceptors, pipes, or when the user mentions NestJS, Nest, or Node.js enterprise applications. +--- + +# NestJS Expert + +A specialized skill for building enterprise-grade, scalable server-side applications with NestJS. This skill covers architecture, best practices, testing, and advanced NestJS features. + +## Instructions + +### Core Workflow + +1. **Understand project requirements** + - Ask about the application type (REST API, GraphQL, Microservices, WebSockets) + - Identify database needs (PostgreSQL, MongoDB, MySQL, etc.) + - Determine authentication requirements (JWT, OAuth, Passport) + - Understand scaling and architecture needs + +2. **Setup and structure** + - Create proper module structure + - Implement dependency injection correctly + - Set up configuration management (@nestjs/config) + - Configure logging and error handling + - Set up validation pipes + +3. **Implement features** + - Create modules, controllers, services following best practices + - Implement proper DTOs with class-validator + - Set up database entities/models + - Implement authentication and authorization + - Add interceptors, guards, and pipes where appropriate + +4. **Testing** + - Write unit tests for services + - Write E2E tests for controllers + - Mock dependencies properly + - Test guards, interceptors, and pipes + +5. **Documentation and deployment** + - Add Swagger/OpenAPI documentation + - Configure for production (environment variables, logging) + - Set up Docker and docker-compose if needed + +### NestJS Architecture Best Practices + +#### Module Organization + +```typescript +// Feature module structure +src/ +├── app.module.ts +├── main.ts +├── common/ // Shared utilities +│ ├── decorators/ +│ ├── filters/ +│ ├── guards/ +│ ├── interceptors/ +│ └── pipes/ +├── config/ // Configuration +│ └── configuration.ts +└── features/ // Feature modules + ├── users/ + │ ├── dto/ + │ │ ├── create-user.dto.ts + │ │ └── update-user.dto.ts + │ ├── entities/ + │ │ └── user.entity.ts + │ ├── users.controller.ts + │ ├── users.service.ts + │ ├── users.module.ts + │ └── users.service.spec.ts + └── auth/ + ├── guards/ + ├── strategies/ + ├── auth.controller.ts + ├── auth.service.ts + └── auth.module.ts +``` + +#### Controller Best Practices + +```typescript +@Controller('users') +@ApiTags('users') +@UseGuards(JwtAuthGuard) +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Post() + @ApiOperation({ summary: 'Create a new user' }) + @ApiResponse({ status: 201, description: 'User created', type: UserDto }) + @ApiResponse({ status: 400, description: 'Invalid input' }) + async create(@Body() createUserDto: CreateUserDto): Promise { + return this.usersService.create(createUserDto); + } + + @Get() + @ApiOperation({ summary: 'Get all users' }) + @ApiQuery({ name: 'page', required: false, type: Number }) + @ApiQuery({ name: 'limit', required: false, type: Number }) + async findAll( + @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, + @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number, + ): Promise> { + return this.usersService.findAll({ page, limit }); + } + + @Get(':id') + @ApiOperation({ summary: 'Get user by ID' }) + @ApiParam({ name: 'id', type: 'string' }) + async findOne(@Param('id', ParseUUIDPipe) id: string): Promise { + return this.usersService.findOne(id); + } + + @Patch(':id') + @ApiOperation({ summary: 'Update user' }) + async update( + @Param('id', ParseUUIDPipe) id: string, + @Body() updateUserDto: UpdateUserDto, + ): Promise { + return this.usersService.update(id, updateUserDto); + } + + @Delete(':id') + @HttpCode(HttpStatus.NO_CONTENT) + @ApiOperation({ summary: 'Delete user' }) + async remove(@Param('id', ParseUUIDPipe) id: string): Promise { + await this.usersService.remove(id); + } +} +``` + +#### Service Layer with Proper Error Handling + +```typescript +@Injectable() +export class UsersService { + private readonly logger = new Logger(UsersService.name); + + constructor( + @InjectRepository(User) + private readonly usersRepository: Repository, + ) {} + + async create(createUserDto: CreateUserDto): Promise { + try { + const user = this.usersRepository.create(createUserDto); + return await this.usersRepository.save(user); + } catch (error) { + this.logger.error(`Failed to create user: ${error.message}`, error.stack); + + if (error.code === '23505') { // PostgreSQL unique violation + throw new ConflictException('User with this email already exists'); + } + + throw new InternalServerErrorException('Failed to create user'); + } + } + + async findAll(options: PaginationOptions): Promise> { + const { page, limit } = options; + const skip = (page - 1) * limit; + + const [users, total] = await this.usersRepository.findAndCount({ + skip, + take: limit, + order: { createdAt: 'DESC' }, + }); + + return { + data: users, + meta: { + page, + limit, + total, + totalPages: Math.ceil(total / limit), + }, + }; + } + + async findOne(id: string): Promise { + const user = await this.usersRepository.findOne({ where: { id } }); + + if (!user) { + throw new NotFoundException(`User with ID ${id} not found`); + } + + return user; + } + + async update(id: string, updateUserDto: UpdateUserDto): Promise { + const user = await this.findOne(id); // Reuse findOne for consistency + + Object.assign(user, updateUserDto); + + return await this.usersRepository.save(user); + } + + async remove(id: string): Promise { + const result = await this.usersRepository.delete(id); + + if (result.affected === 0) { + throw new NotFoundException(`User with ID ${id} not found`); + } + } +} +``` + +#### DTOs with Validation + +```typescript +// create-user.dto.ts +import { ApiProperty } from '@nestjs/swagger'; +import { + IsEmail, + IsString, + MinLength, + MaxLength, + IsOptional, + Matches +} from 'class-validator'; + +export class CreateUserDto { + @ApiProperty({ + description: 'User email address', + example: 'user@example.com', + }) + @IsEmail() + email: string; + + @ApiProperty({ + description: 'User password (min 8 characters, must contain uppercase, lowercase, and number)', + example: 'SecurePass123', + minLength: 8, + }) + @IsString() + @MinLength(8) + @Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, { + message: 'Password must contain uppercase, lowercase, and number', + }) + password: string; + + @ApiProperty({ + description: 'User full name', + example: 'John Doe', + minLength: 2, + maxLength: 100, + }) + @IsString() + @MinLength(2) + @MaxLength(100) + name: string; + + @ApiProperty({ + description: 'User role', + example: 'user', + required: false, + enum: ['user', 'admin'], + }) + @IsOptional() + @IsIn(['user', 'admin']) + role?: string = 'user'; +} + +// update-user.dto.ts +import { PartialType, OmitType } from '@nestjs/swagger'; +import { CreateUserDto } from './create-user.dto'; + +export class UpdateUserDto extends PartialType( + OmitType(CreateUserDto, ['password'] as const) +) {} +``` + +#### Authentication with JWT + +```typescript +// auth.service.ts +@Injectable() +export class AuthService { + constructor( + private usersService: UsersService, + private jwtService: JwtService, + ) {} + + async validateUser(email: string, password: string): Promise { + const user = await this.usersService.findByEmail(email); + + if (user && await bcrypt.compare(password, user.password)) { + const { password, ...result } = user; + return result; + } + + return null; + } + + async login(user: any) { + const payload = { email: user.email, sub: user.id, role: user.role }; + + return { + access_token: this.jwtService.sign(payload), + user: { + id: user.id, + email: user.email, + name: user.name, + }, + }; + } + + async register(createUserDto: CreateUserDto) { + const hashedPassword = await bcrypt.hash(createUserDto.password, 10); + + const user = await this.usersService.create({ + ...createUserDto, + password: hashedPassword, + }); + + return this.login(user); + } +} + +// jwt.strategy.ts +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET'), + }); + } + + async validate(payload: any) { + return { + userId: payload.sub, + email: payload.email, + role: payload.role + }; + } +} + +// jwt-auth.guard.ts +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') { + canActivate(context: ExecutionContext) { + return super.canActivate(context); + } + + handleRequest(err, user, info) { + if (err || !user) { + throw err || new UnauthorizedException(); + } + return user; + } +} +``` + +#### Role-Based Access Control + +```typescript +// roles.decorator.ts +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); + +// roles.guard.ts +@Injectable() +export class RolesGuard implements CanActivate { + constructor(private reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const requiredRoles = this.reflector.getAllAndOverride( + ROLES_KEY, + [context.getHandler(), context.getClass()], + ); + + if (!requiredRoles) { + return true; + } + + const { user } = context.switchToHttp().getRequest(); + return requiredRoles.some((role) => user.role === role); + } +} + +// Usage +@Controller('admin') +@UseGuards(JwtAuthGuard, RolesGuard) +export class AdminController { + @Get('users') + @Roles('admin') + getAllUsers() { + // Only accessible by admins + } +} +``` + +#### Custom Interceptor (Logging) + +```typescript +@Injectable() +export class LoggingInterceptor implements NestInterceptor { + private readonly logger = new Logger(LoggingInterceptor.name); + + intercept(context: ExecutionContext, next: CallHandler): Observable { + const request = context.switchToHttp().getRequest(); + const { method, url, body } = request; + const now = Date.now(); + + this.logger.log(`Incoming Request: ${method} ${url}`); + + return next.handle().pipe( + tap(() => { + const response = context.switchToHttp().getResponse(); + const delay = Date.now() - now; + + this.logger.log( + `Outgoing Response: ${method} ${url} ${response.statusCode} - ${delay}ms` + ); + }), + catchError((error) => { + const delay = Date.now() - now; + this.logger.error( + `Request Failed: ${method} ${url} - ${delay}ms`, + error.stack + ); + throw error; + }), + ); + } +} +``` + +#### Exception Filters + +```typescript +@Catch(HttpException) +export class HttpExceptionFilter implements ExceptionFilter { + private readonly logger = new Logger(HttpExceptionFilter.name); + + catch(exception: HttpException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception.getStatus(); + const exceptionResponse = exception.getResponse(); + + const errorResponse = { + statusCode: status, + timestamp: new Date().toISOString(), + path: request.url, + method: request.method, + message: + typeof exceptionResponse === 'string' + ? exceptionResponse + : (exceptionResponse as any).message || 'Internal server error', + }; + + this.logger.error( + `${request.method} ${request.url}`, + JSON.stringify(errorResponse), + exception.stack, + ); + + response.status(status).json(errorResponse); + } +} + +// Register globally in main.ts +app.useGlobalFilters(new HttpExceptionFilter()); +``` + +#### Configuration Management + +```typescript +// configuration.ts +export default () => ({ + port: parseInt(process.env.PORT, 10) || 3000, + database: { + host: process.env.DATABASE_HOST || 'localhost', + port: parseInt(process.env.DATABASE_PORT, 10) || 5432, + username: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + database: process.env.DATABASE_NAME, + }, + jwt: { + secret: process.env.JWT_SECRET, + expiresIn: process.env.JWT_EXPIRES_IN || '1d', + }, + redis: { + host: process.env.REDIS_HOST || 'localhost', + port: parseInt(process.env.REDIS_PORT, 10) || 6379, + }, +}); + +// app.module.ts +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [configuration], + validationSchema: Joi.object({ + NODE_ENV: Joi.string() + .valid('development', 'production', 'test') + .default('development'), + PORT: Joi.number().default(3000), + DATABASE_HOST: Joi.string().required(), + DATABASE_PORT: Joi.number().default(5432), + JWT_SECRET: Joi.string().required(), + }), + }), + ], +}) +export class AppModule {} +``` + +#### Testing + +```typescript +// users.service.spec.ts +describe('UsersService', () => { + let service: UsersService; + let repository: Repository; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + UsersService, + { + provide: getRepositoryToken(User), + useClass: Repository, + }, + ], + }).compile(); + + service = module.get(UsersService); + repository = module.get>(getRepositoryToken(User)); + }); + + describe('create', () => { + it('should create a user', async () => { + const createUserDto: CreateUserDto = { + email: 'test@example.com', + password: 'Password123', + name: 'Test User', + }; + + const expectedUser = { id: '1', ...createUserDto }; + + jest.spyOn(repository, 'create').mockReturnValue(expectedUser as any); + jest.spyOn(repository, 'save').mockResolvedValue(expectedUser as any); + + const result = await service.create(createUserDto); + + expect(result).toEqual(expectedUser); + expect(repository.create).toHaveBeenCalledWith(createUserDto); + expect(repository.save).toHaveBeenCalledWith(expectedUser); + }); + }); + + describe('findOne', () => { + it('should throw NotFoundException if user not found', async () => { + jest.spyOn(repository, 'findOne').mockResolvedValue(null); + + await expect(service.findOne('1')).rejects.toThrow(NotFoundException); + }); + }); +}); + +// E2E Test +describe('UsersController (e2e)', () => { + let app: INestApplication; + let authToken: string; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [AppModule], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.useGlobalPipes(new ValidationPipe()); + await app.init(); + + // Get auth token + const response = await request(app.getHttpServer()) + .post('/auth/login') + .send({ email: 'admin@example.com', password: 'password' }); + + authToken = response.body.access_token; + }); + + it('/users (POST)', () => { + return request(app.getHttpServer()) + .post('/users') + .set('Authorization', `Bearer ${authToken}`) + .send({ + email: 'newuser@example.com', + password: 'Password123', + name: 'New User', + }) + .expect(201) + .expect((res) => { + expect(res.body).toHaveProperty('id'); + expect(res.body.email).toBe('newuser@example.com'); + }); + }); + + afterAll(async () => { + await app.close(); + }); +}); +``` + +## Critical Rules + +### Always Do +- Use dependency injection for all services +- Validate all incoming data with class-validator +- Use DTOs for all request/response bodies +- Implement proper error handling and logging +- Use guards for authentication/authorization +- Document APIs with Swagger decorators +- Write unit and E2E tests +- Use environment variables for configuration +- Implement proper module organization +- Use TypeScript strict mode + +### Never Do +- Never expose sensitive data in responses (passwords, secrets) +- Never trust user input without validation +- Never use synchronous operations in services +- Never hardcode configuration values +- Never skip error handling +- Never ignore security best practices +- Never use any type unless absolutely necessary +- Never create circular dependencies between modules + +## Knowledge Base + +- **NestJS Core**: Modules, Controllers, Providers, Middleware, Guards, Interceptors, Pipes +- **TypeORM/Prisma**: Database integration and ORM patterns +- **Authentication**: JWT, Passport, OAuth, Session management +- **Testing**: Jest, Supertest, E2E testing patterns +- **GraphQL**: GraphQL with NestJS (@nestjs/graphql) +- **Microservices**: TCP, Redis, NATS, gRPC transport layers +- **WebSockets**: Real-time communication with Socket.io +- **Caching**: Redis integration for caching +- **Queue**: Bull/BullMQ for background jobs +- **Documentation**: Swagger/OpenAPI integration + +## Integration with Other Skills + +- **Works with**: Fullstack Guardian, Test Master, DevOps Engineer +- **Complements**: Code Documenter (for Swagger docs), Security Reviewer + +## Best Practices Summary + +1. **Module Design**: Feature-based modules with clear boundaries +2. **Dependency Injection**: Use DI for all service dependencies +3. **Validation**: Validate all inputs with class-validator +4. **Error Handling**: Centralized error handling with filters +5. **Security**: JWT auth, RBAC, input validation, helmet middleware +6. **Testing**: High test coverage with unit and E2E tests +7. **Documentation**: Comprehensive Swagger documentation +8. **Configuration**: Environment-based configuration management +9. **Logging**: Structured logging with context +10. **Performance**: Caching, database query optimization, async operations diff --git a/skills/playwright-expert/SKILL.md b/skills/playwright-expert/SKILL.md new file mode 100644 index 0000000..a58c099 --- /dev/null +++ b/skills/playwright-expert/SKILL.md @@ -0,0 +1,470 @@ +--- +name: Playwright Expert +description: Expert in Playwright end-to-end testing for web applications. Use when writing E2E tests, debugging Playwright tests, setting up test infrastructure, handling test flakiness, or when the user mentions Playwright, browser testing, E2E testing, or UI automation. +--- + +# Playwright Expert + +A specialized skill for creating robust, maintainable end-to-end tests using Playwright. This skill helps you write reliable browser automation tests for modern web applications. + +## Instructions + +### Core Workflow + +1. **Understand testing requirements** + - Ask what needs to be tested (user flows, features, pages) + - Identify the application framework (React, Vue, Angular, etc.) + - Determine test scope (smoke tests, regression, full E2E suite) + - Ask about existing test infrastructure + +2. **Setup and configuration** + - Verify Playwright installation + - Configure playwright.config.ts appropriately + - Set up test project structure + - Configure browsers, viewports, base URLs + - Set up CI/CD integration if needed + +3. **Write maintainable tests** + - Use Page Object Model (POM) pattern for complex apps + - Implement proper selectors (prefer data-testid, role-based) + - Add proper waits and assertions + - Handle authentication and state management + - Write reusable fixtures and helpers + +4. **Debug and fix flaky tests** + - Use Playwright's debugging tools + - Identify race conditions and timing issues + - Implement proper wait strategies + - Add trace collection for failures + +5. **Best practices** + - Keep tests independent and isolated + - Use meaningful test descriptions + - Implement proper cleanup and teardown + - Optimize test execution (parallel, sharding) + +### Playwright Best Practices + +#### Selector Strategy (Priority Order) +1. **User-facing attributes** (best): + ```typescript + await page.getByRole('button', { name: 'Submit' }); + await page.getByLabel('Email address'); + await page.getByPlaceholder('Enter email'); + await page.getByText('Welcome'); + ``` + +2. **Data-testid** (good for non-semantic elements): + ```typescript + await page.getByTestId('user-profile'); + ``` + +3. **CSS/XPath** (avoid if possible, brittle): + ```typescript + await page.locator('.submit-button'); + ``` + +#### Wait Strategies +```typescript +// Auto-waiting (preferred - built into Playwright) +await page.click('button'); // Automatically waits for element + +// Explicit waits when needed +await page.waitForSelector('[data-testid="results"]'); +await page.waitForResponse(resp => resp.url().includes('/api/data')); +await page.waitForLoadState('networkidle'); + +// Assertions with auto-retry +await expect(page.getByText('Success')).toBeVisible(); +await expect(page).toHaveURL(/dashboard/); +``` + +#### Page Object Model Pattern +```typescript +// pages/LoginPage.ts +export class LoginPage { + readonly page: Page; + readonly emailInput: Locator; + readonly passwordInput: Locator; + readonly submitButton: Locator; + + constructor(page: Page) { + this.page = page; + this.emailInput = page.getByLabel('Email'); + this.passwordInput = page.getByLabel('Password'); + this.submitButton = page.getByRole('button', { name: 'Log in' }); + } + + async login(email: string, password: string) { + await this.emailInput.fill(email); + await this.passwordInput.fill(password); + await this.submitButton.click(); + await this.page.waitForURL(/dashboard/); + } + + async goto() { + await this.page.goto('/login'); + } +} + +// test.spec.ts +test('user can login', async ({ page }) => { + const loginPage = new LoginPage(page); + await loginPage.goto(); + await loginPage.login('user@example.com', 'password'); + await expect(page.getByText('Welcome')).toBeVisible(); +}); +``` + +#### Fixtures for Reusability +```typescript +// fixtures.ts +import { test as base } from '@playwright/test'; + +type Fixtures = { + authenticatedPage: Page; +}; + +export const test = base.extend({ + authenticatedPage: async ({ page }, use) => { + // Login before each test + await page.goto('/login'); + await page.fill('[name="email"]', 'user@example.com'); + await page.fill('[name="password"]', 'password'); + await page.click('button[type="submit"]'); + await page.waitForURL(/dashboard/); + + await use(page); + + // Cleanup if needed + }, +}); + +// Usage +test('view profile', async ({ authenticatedPage }) => { + await authenticatedPage.goto('/profile'); + // Test authenticated features +}); +``` + +#### API Mocking and Interception +```typescript +// Mock API responses +await page.route('**/api/users', route => { + route.fulfill({ + status: 200, + body: JSON.stringify([ + { id: 1, name: 'John Doe' }, + { id: 2, name: 'Jane Smith' } + ]) + }); +}); + +// Intercept and modify requests +await page.route('**/api/config', route => { + const response = await route.fetch(); + const json = await response.json(); + json.feature_flag_enabled = true; + route.fulfill({ response, json }); +}); +``` + +#### Handling Authentication +```typescript +// Save auth state +test('save auth state', async ({ page }) => { + await page.goto('/login'); + await page.fill('[name="email"]', 'user@example.com'); + await page.fill('[name="password"]', 'password'); + await page.click('button[type="submit"]'); + await page.waitForURL(/dashboard/); + + // Save storage state + await page.context().storageState({ path: 'auth.json' }); +}); + +// Reuse auth state in config +// playwright.config.ts +export default defineConfig({ + use: { + storageState: 'auth.json', + }, +}); +``` + +#### Screenshot and Video +```typescript +// Take screenshots +await page.screenshot({ path: 'screenshot.png', fullPage: true }); + +// Configure in playwright.config.ts +export default defineConfig({ + use: { + screenshot: 'only-on-failure', + video: 'retain-on-failure', + trace: 'retain-on-failure', + }, +}); +``` + +### Debugging Flaky Tests + +#### Common Causes and Solutions + +1. **Race Conditions** + ```typescript + // Bad: Doesn't wait for API call + await page.click('button'); + await expect(page.getByText('Success')).toBeVisible(); + + // Good: Wait for API response + await Promise.all([ + page.waitForResponse(resp => resp.url().includes('/api/submit')), + page.click('button') + ]); + await expect(page.getByText('Success')).toBeVisible(); + ``` + +2. **Animation/Transition Issues** + ```typescript + // Disable animations in test mode + // playwright.config.ts + use: { + actionTimeout: 10000, + navigationTimeout: 30000, + } + + // Or add CSS to disable animations + await page.addStyleTag({ + content: '* { animation: none !important; transition: none !important; }' + }); + ``` + +3. **Non-deterministic Selectors** + ```typescript + // Bad: Order-dependent + await page.locator('.user-card').first().click(); + + // Good: Specific identifier + await page.getByTestId('user-card-123').click(); + ``` + +4. **Timing Issues** + ```typescript + // Use Playwright's auto-waiting instead of manual delays + // Bad + await page.click('button'); + await page.waitForTimeout(3000); // Arbitrary delay + + // Good + await page.click('button'); + await page.waitForSelector('[data-testid="result"]'); + // or + await expect(page.getByTestId('result')).toBeVisible(); + ``` + +### Debug Tools + +```typescript +// Debug mode +await page.pause(); // Opens Playwright Inspector + +// Slow down execution +test.use({ launchOptions: { slowMo: 100 } }); + +// Enable verbose logging +DEBUG=pw:api npx playwright test + +// Trace viewer (after test runs with trace: 'on') +npx playwright show-trace trace.zip +``` + +### Configuration Best Practices + +```typescript +// playwright.config.ts +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [ + ['html'], + ['json', { outputFile: 'test-results.json' }], + ['junit', { outputFile: 'test-results.xml' }] + ], + + use: { + baseURL: process.env.BASE_URL || 'http://localhost:3000', + trace: 'retain-on-failure', + screenshot: 'only-on-failure', + video: 'retain-on-failure', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + // Mobile + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + ], + + webServer: { + command: 'npm run dev', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + }, +}); +``` + +### CI/CD Integration + +```yaml +# GitHub Actions example +name: Playwright Tests +on: [push, pull_request] +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: npm ci + - name: Install Playwright Browsers + run: npx playwright install --with-deps + - name: Run Playwright tests + run: npx playwright test + - uses: actions/upload-artifact@v3 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 +``` + +## Critical Rules + +### Always Do +- Use auto-waiting features (don't add arbitrary timeouts) +- Prefer user-facing selectors (getByRole, getByLabel, etc.) +- Keep tests independent (no shared state between tests) +- Use Page Object Model for complex applications +- Add proper assertions (use expect with auto-retry) +- Enable traces and screenshots for debugging +- Run tests in parallel when possible +- Use fixtures for common setup + +### Never Do +- Never use `waitForTimeout()` unless absolutely necessary +- Never rely on brittle CSS selectors (prefer data-testid or semantic selectors) +- Never share state between tests +- Never ignore flaky tests (fix them!) +- Never commit `.auth.json` or sensitive data +- Never use `first()`, `last()`, `nth()` without good reason (non-deterministic) + +## Knowledge Base + +- **Playwright API**: Expert in all Playwright APIs and features +- **Browser Automation**: Deep understanding of browser automation challenges +- **Test Patterns**: Page Object Model, fixtures, custom matchers +- **Debugging**: Proficient in debugging flaky tests and race conditions +- **CI/CD**: Experience integrating Playwright with various CI systems +- **Performance**: Understanding test optimization and parallelization + +## Integration with Other Skills + +- **Works with**: Test Master (overall testing strategy), React Expert (testing React apps) +- **Complements**: Frontend framework skills for framework-specific testing approaches + +## Examples + +### Example 1: E2E Test for User Registration + +```typescript +import { test, expect } from '@playwright/test'; + +test.describe('User Registration', () => { + test('successful registration flow', async ({ page }) => { + await page.goto('/register'); + + // Fill form + await page.getByLabel('Email').fill('newuser@example.com'); + await page.getByLabel('Password').fill('SecurePass123!'); + await page.getByLabel('Confirm Password').fill('SecurePass123!'); + await page.getByLabel(/I agree to the terms/).check(); + + // Submit and wait for navigation + await page.getByRole('button', { name: 'Sign Up' }).click(); + + // Verify success + await expect(page).toHaveURL(/dashboard/); + await expect(page.getByText('Welcome')).toBeVisible(); + }); + + test('validates email format', async ({ page }) => { + await page.goto('/register'); + + await page.getByLabel('Email').fill('invalid-email'); + await page.getByLabel('Password').fill('SecurePass123!'); + await page.getByRole('button', { name: 'Sign Up' }).click(); + + await expect(page.getByText('Please enter a valid email')).toBeVisible(); + await expect(page).toHaveURL(/register/); + }); +}); +``` + +### Example 2: Testing with API Mocking + +```typescript +test('displays user list from API', async ({ page }) => { + // Mock the API response + await page.route('**/api/users', route => { + route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ + users: [ + { id: 1, name: 'Alice', email: 'alice@example.com' }, + { id: 2, name: 'Bob', email: 'bob@example.com' } + ] + }) + }); + }); + + await page.goto('/users'); + + // Verify mocked data is displayed + await expect(page.getByText('Alice')).toBeVisible(); + await expect(page.getByText('Bob')).toBeVisible(); +}); +``` + +## Best Practices Summary + +1. **Selector Priority**: Role > Label > Placeholder > Test ID > CSS/XPath +2. **Waiting**: Use auto-waiting, avoid `waitForTimeout()` +3. **Independence**: Each test should run in isolation +4. **Patterns**: Use Page Object Model for maintainability +5. **Debugging**: Enable traces, screenshots, and videos on failure +6. **Performance**: Run tests in parallel, use sharding for large suites +7. **Stability**: Fix flaky tests immediately, don't retry indefinitely +8. **Assertions**: Use Playwright's expect with built-in retry logic +9. **Authentication**: Save and reuse auth state +10. **CI/CD**: Integrate early, run on every commit diff --git a/skills/react-expert/SKILL.md b/skills/react-expert/SKILL.md new file mode 100644 index 0000000..94e10a7 --- /dev/null +++ b/skills/react-expert/SKILL.md @@ -0,0 +1,559 @@ +--- +name: React Expert +description: Expert in React for building modern web applications. Use when working with React, React hooks, state management (Redux, Zustand, Context), component architecture, performance optimization, or when the user mentions React, JSX, functional components, hooks, or frontend development. +--- + +# React Expert + +A specialized skill for building production-ready React applications with modern patterns, hooks, performance optimization, and best practices. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify component needs and hierarchy + - Determine state management requirements (local, Context, Redux, Zustand) + - Understand performance requirements + - Identify routing needs (React Router) + +2. **Project structure** + - Organize components logically + - Separate concerns (components, hooks, utils, types) + - Configure TypeScript for type safety + - Set up testing infrastructure + +3. **Implement features** + - Create reusable components + - Implement custom hooks + - Manage state appropriately + - Optimize performance (memo, useMemo, useCallback) + - Handle side effects properly + +4. **Testing and optimization** + - Write tests with React Testing Library + - Optimize bundle size + - Implement code splitting + - Profile and optimize performance + +### React Project Structure + +``` +src/ +├── components/ +│ ├── common/ # Reusable components +│ │ ├── Button/ +│ │ │ ├── Button.tsx +│ │ │ ├── Button.test.tsx +│ │ │ └── Button.module.css +│ │ └── Input/ +│ ├── features/ # Feature-specific components +│ │ ├── auth/ +│ │ └── dashboard/ +│ └── layout/ # Layout components +│ ├── Header.tsx +│ └── Footer.tsx +├── hooks/ # Custom hooks +│ ├── useAuth.ts +│ ├── useApi.ts +│ └── useLocalStorage.ts +├── contexts/ # Context providers +│ └── AuthContext.tsx +├── store/ # State management (Redux/Zustand) +│ ├── slices/ +│ └── store.ts +├── services/ # API services +│ └── api.ts +├── utils/ # Utility functions +│ └── format.ts +├── types/ # TypeScript types +│ └── index.ts +├── pages/ # Page components (if using routing) +│ ├── Home.tsx +│ └── Dashboard.tsx +└── App.tsx +``` + +### Modern Component Patterns + +```typescript +// Functional component with TypeScript +import { FC, useState, useEffect, useMemo, useCallback } from 'react'; + +interface User { + id: number; + name: string; + email: string; +} + +interface UserListProps { + initialUsers?: User[]; + onUserSelect?: (user: User) => void; +} + +export const UserList: FC = ({ + initialUsers = [], + onUserSelect +}) => { + const [users, setUsers] = useState(initialUsers); + const [searchTerm, setSearchTerm] = useState(''); + const [loading, setLoading] = useState(false); + + // Fetch users on mount + useEffect(() => { + const fetchUsers = async () => { + setLoading(true); + try { + const response = await fetch('/api/users'); + const data = await response.json(); + setUsers(data); + } catch (error) { + console.error('Failed to fetch users:', error); + } finally { + setLoading(false); + } + }; + + if (initialUsers.length === 0) { + fetchUsers(); + } + }, [initialUsers.length]); + + // Memoized filtered users + const filteredUsers = useMemo(() => { + return users.filter(user => + user.name.toLowerCase().includes(searchTerm.toLowerCase()) || + user.email.toLowerCase().includes(searchTerm.toLowerCase()) + ); + }, [users, searchTerm]); + + // Memoized callback + const handleUserClick = useCallback((user: User) => { + onUserSelect?.(user); + }, [onUserSelect]); + + if (loading) { + return
Loading...
; + } + + return ( +
+ setSearchTerm(e.target.value)} + /> +
    + {filteredUsers.map(user => ( +
  • handleUserClick(user)}> + {user.name} - {user.email} +
  • + ))} +
+
+ ); +}; +``` + +### Custom Hooks + +```typescript +// useApi.ts - Generic API hook +import { useState, useEffect } from 'react'; + +interface UseApiOptions { + url: string; + method?: 'GET' | 'POST' | 'PUT' | 'DELETE'; + body?: any; + dependencies?: any[]; +} + +interface UseApiReturn { + data: T | null; + loading: boolean; + error: Error | null; + refetch: () => Promise; +} + +export function useApi({ + url, + method = 'GET', + body, + dependencies = [] +}: UseApiOptions): UseApiReturn { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = async () => { + setLoading(true); + setError(null); + + try { + const response = await fetch(url, { + method, + headers: { + 'Content-Type': 'application/json', + }, + body: body ? JSON.stringify(body) : undefined, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const result = await response.json(); + setData(result); + } catch (err) { + setError(err as Error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchData(); + }, dependencies); + + return { data, loading, error, refetch: fetchData }; +} + +// useLocalStorage.ts +import { useState, useEffect } from 'react'; + +export function useLocalStorage(key: string, initialValue: T) { + const [storedValue, setStoredValue] = useState(() => { + try { + const item = window.localStorage.getItem(key); + return item ? JSON.parse(item) : initialValue; + } catch (error) { + console.error(error); + return initialValue; + } + }); + + const setValue = (value: T | ((val: T) => T)) => { + try { + const valueToStore = value instanceof Function ? value(storedValue) : value; + setStoredValue(valueToStore); + window.localStorage.setItem(key, JSON.stringify(valueToStore)); + } catch (error) { + console.error(error); + } + }; + + return [storedValue, setValue] as const; +} + +// useDebounce.ts +import { useState, useEffect } from 'react'; + +export function useDebounce(value: T, delay: number): T { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} +``` + +### Context API for State Management + +```typescript +// AuthContext.tsx +import { createContext, useContext, useState, useEffect, ReactNode } from 'react'; + +interface User { + id: number; + name: string; + email: string; +} + +interface AuthContextType { + user: User | null; + loading: boolean; + login: (email: string, password: string) => Promise; + logout: () => void; + isAuthenticated: boolean; +} + +const AuthContext = createContext(undefined); + +export const AuthProvider = ({ children }: { children: ReactNode }) => { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + // Check for existing session + const checkAuth = async () => { + try { + const response = await fetch('/api/auth/me'); + if (response.ok) { + const userData = await response.json(); + setUser(userData); + } + } catch (error) { + console.error('Auth check failed:', error); + } finally { + setLoading(false); + } + }; + + checkAuth(); + }, []); + + const login = async (email: string, password: string) => { + const response = await fetch('/api/auth/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }), + }); + + if (!response.ok) { + throw new Error('Login failed'); + } + + const userData = await response.json(); + setUser(userData); + }; + + const logout = () => { + fetch('/api/auth/logout', { method: 'POST' }); + setUser(null); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => { + const context = useContext(AuthContext); + if (!context) { + throw new Error('useAuth must be used within AuthProvider'); + } + return context; +}; +``` + +### Performance Optimization + +```typescript +import { memo, useMemo, useCallback } from 'react'; + +// Memoized component (only re-renders if props change) +export const ExpensiveComponent = memo(({ data }: { data: any[] }) => { + return ( +
+ {data.map(item => ( +
{item.name}
+ ))} +
+ ); +}); + +// Parent component +export const ParentComponent = () => { + const [count, setCount] = useState(0); + const [users, setUsers] = useState([]); + + // Memoize expensive calculations + const sortedUsers = useMemo(() => { + return [...users].sort((a, b) => a.name.localeCompare(b.name)); + }, [users]); + + // Memoize callbacks to prevent child re-renders + const handleUserAdd = useCallback((user) => { + setUsers(prev => [...prev, user]); + }, []); + + return ( +
+ + {/* ExpensiveComponent won't re-render when count changes */} + +
+ ); +}; +``` + +### Form Handling with React Hook Form + +```typescript +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; + +const schema = z.object({ + email: z.string().email('Invalid email'), + password: z.string().min(8, 'Password must be at least 8 characters'), + confirmPassword: z.string(), +}).refine((data) => data.password === data.confirmPassword, { + message: "Passwords don't match", + path: ["confirmPassword"], +}); + +type FormData = z.infer; + +export const RegisterForm = () => { + const { + register, + handleSubmit, + formState: { errors, isSubmitting }, + } = useForm({ + resolver: zodResolver(schema), + }); + + const onSubmit = async (data: FormData) => { + try { + await fetch('/api/register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data), + }); + } catch (error) { + console.error('Registration failed:', error); + } + }; + + return ( +
+
+ + {errors.email && {errors.email.message}} +
+ +
+ + {errors.password && {errors.password.message}} +
+ +
+ + {errors.confirmPassword && {errors.confirmPassword.message}} +
+ + +
+ ); +}; +``` + +### Testing with React Testing Library + +```typescript +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { UserList } from './UserList'; + +describe('UserList', () => { + const mockUsers = [ + { id: 1, name: 'John Doe', email: 'john@example.com' }, + { id: 2, name: 'Jane Smith', email: 'jane@example.com' }, + ]; + + it('renders users', () => { + render(); + + expect(screen.getByText('John Doe - john@example.com')).toBeInTheDocument(); + expect(screen.getByText('Jane Smith - jane@example.com')).toBeInTheDocument(); + }); + + it('filters users by search term', async () => { + render(); + + const searchInput = screen.getByPlaceholderText('Search users...'); + await userEvent.type(searchInput, 'John'); + + expect(screen.getByText('John Doe - john@example.com')).toBeInTheDocument(); + expect(screen.queryByText('Jane Smith - jane@example.com')).not.toBeInTheDocument(); + }); + + it('calls onUserSelect when user is clicked', async () => { + const onUserSelect = jest.fn(); + render(); + + const firstUser = screen.getByText('John Doe - john@example.com'); + fireEvent.click(firstUser); + + expect(onUserSelect).toHaveBeenCalledWith(mockUsers[0]); + }); + + it('shows loading state', () => { + render(); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); +}); +``` + +## Critical Rules + +### Always Do +- Use TypeScript for type safety +- Implement proper error boundaries +- Memoize expensive calculations with useMemo +- Memoize callbacks with useCallback when passing to child components +- Use React.memo for components that render often with same props +- Keep components small and focused +- Extract reusable logic into custom hooks +- Write tests for components +- Use proper key props in lists +- Handle loading and error states + +### Never Do +- Never mutate state directly +- Never use index as key in dynamic lists +- Never forget cleanup in useEffect +- Never create functions inside JSX (causes re-renders) +- Never ignore ESLint warnings +- Never use inline styles for everything (use CSS modules or styled-components) +- Never skip error boundaries +- Never ignore accessibility (use semantic HTML, ARIA labels) + +## Knowledge Base + +- **React Core**: Hooks, Context, Suspense, Error Boundaries +- **State Management**: Redux, Zustand, Jotai, Context API +- **Routing**: React Router, TanStack Router +- **Forms**: React Hook Form, Formik +- **Testing**: React Testing Library, Jest +- **Styling**: CSS Modules, Styled Components, Tailwind CSS +- **Performance**: Code splitting, lazy loading, memoization +- **TypeScript**: Type safety, generics, utility types + +## Integration with Other Skills + +- **Works with**: Fullstack Guardian, Playwright Expert, Test Master +- **Complements**: React Native Expert (mobile), Code Reviewer + +## Best Practices Summary + +1. **TypeScript**: Use for all components and hooks +2. **Hooks**: Prefer functional components with hooks +3. **Custom Hooks**: Extract reusable logic +4. **Performance**: Memo, useMemo, useCallback where needed +5. **Testing**: Comprehensive tests with React Testing Library +6. **Accessibility**: Semantic HTML, ARIA labels, keyboard navigation +7. **Code Splitting**: Lazy load routes and heavy components +8. **Error Handling**: Error boundaries for graceful degradation +9. **State Management**: Choose appropriate solution for complexity +10. **Clean Code**: Small, focused, well-named components diff --git a/skills/react-native-expert/SKILL.md b/skills/react-native-expert/SKILL.md new file mode 100644 index 0000000..dbdb5cd --- /dev/null +++ b/skills/react-native-expert/SKILL.md @@ -0,0 +1,442 @@ +--- +name: React Native Expert +description: Expert in React Native for building cross-platform mobile applications. Use when working with React Native, Expo, mobile development, native modules, navigation, or when the user mentions React Native, mobile apps, iOS, Android, Expo, or cross-platform development. +--- + +# React Native Expert + +A specialized skill for building production-ready cross-platform mobile applications with React Native and Expo. + +## Instructions + +### Core Workflow + +1. **Understand requirements** + - Identify if using Expo or bare React Native + - Determine platform-specific needs (iOS/Android) + - Understand navigation requirements + - Identify native module needs + +2. **Project setup** + - Choose between Expo and bare React Native + - Set up navigation (React Navigation) + - Configure TypeScript + - Set up state management + +3. **Implement features** + - Create reusable components + - Implement platform-specific code when needed + - Handle device capabilities (camera, location, etc.) + - Optimize performance for mobile + +4. **Testing and deployment** + - Test on both iOS and Android + - Optimize app size and performance + - Configure app deployment (App Store, Google Play) + +### React Native Project Structure (Expo) + +``` +myapp/ +├── app/ # App directory (Expo Router) +│ ├── (tabs)/ +│ │ ├── index.tsx +│ │ └── profile.tsx +│ ├── _layout.tsx +│ └── +not-found.tsx +├── components/ +│ ├── common/ +│ └── features/ +├── hooks/ +│ ├── useAuth.ts +│ └── useAsync.ts +├── services/ +│ └── api.ts +├── constants/ +│ ├── Colors.ts +│ └── Layout.ts +├── utils/ +│ └── storage.ts +├── types/ +│ └── index.ts +├── app.json +└── package.json +``` + +### Component Patterns + +```typescript +import { View, Text, StyleSheet, Platform, Pressable } from 'react-native'; +import { FC } from 'react'; + +interface ButtonProps { + title: string; + onPress: () => void; + variant?: 'primary' | 'secondary'; + disabled?: boolean; +} + +export const Button: FC = ({ + title, + onPress, + variant = 'primary', + disabled = false, +}) => { + return ( + [ + styles.button, + variant === 'primary' ? styles.primaryButton : styles.secondaryButton, + pressed && styles.pressed, + disabled && styles.disabled, + ]} + > + + {title} + + + ); +}; + +const styles = StyleSheet.create({ + button: { + paddingVertical: 12, + paddingHorizontal: 24, + borderRadius: 8, + alignItems: 'center', + justifyContent: 'center', + ...Platform.select({ + ios: { + shadowColor: '#000', + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + }, + android: { + elevation: 5, + }, + }), + }, + primaryButton: { + backgroundColor: '#007AFF', + }, + secondaryButton: { + backgroundColor: '#E5E5EA', + }, + pressed: { + opacity: 0.7, + }, + disabled: { + opacity: 0.5, + }, + text: { + fontSize: 16, + fontWeight: '600', + }, + primaryText: { + color: '#FFFFFF', + }, + secondaryText: { + color: '#000000', + }, +}); +``` + +### Navigation (React Navigation) + +```typescript +// app/_layout.tsx (Expo Router) +import { Stack } from 'expo-router'; + +export default function RootLayout() { + return ( + + + + + ); +} + +// Or with React Navigation +import { NavigationContainer } from '@react-navigation/native'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +type RootStackParamList = { + Home: undefined; + Profile: { userId: string }; + Details: { itemId: number }; +}; + +const Stack = createNativeStackNavigator(); + +export default function App() { + return ( + + + + + + + + ); +} + +// Type-safe navigation +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; + +type ProfileScreenNavigationProp = NativeStackNavigationProp< + RootStackParamList, + 'Profile' +>; + +const ProfileScreen = () => { + const navigation = useNavigation(); + + const handlePress = () => { + navigation.navigate('Details', { itemId: 42 }); + }; + + return