Initial commit
This commit is contained in:
13
skills/bulletproof-react-auditor/CHANGELOG.md
Normal file
13
skills/bulletproof-react-auditor/CHANGELOG.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Refactored to Anthropic progressive disclosure pattern
|
||||
- Updated description with "Use PROACTIVELY when..." format
|
||||
- Extracted detailed content to workflow/, reference/, examples/ directories
|
||||
|
||||
## 0.1.0
|
||||
|
||||
- Initial skill release
|
||||
- React codebase auditing against Bulletproof React architecture
|
||||
- Anti-pattern detection and migration planning
|
||||
388
skills/bulletproof-react-auditor/README.md
Normal file
388
skills/bulletproof-react-auditor/README.md
Normal file
@@ -0,0 +1,388 @@
|
||||
# Bulletproof React Auditor Skill
|
||||
|
||||
> Comprehensive audit tool for React/TypeScript codebases based on Bulletproof React architecture principles
|
||||
|
||||
An Anthropic Skill that analyzes React applications for architectural issues, component anti-patterns, state management problems, and generates prioritized migration plans for adopting Bulletproof React patterns.
|
||||
|
||||
## Features
|
||||
|
||||
- **Progressive Disclosure**: Three-phase analysis (Discovery → Deep Analysis → Migration Plan)
|
||||
- **React-Specific**: Tailored for React 16.8+ (hooks-based applications)
|
||||
- **Comprehensive Analysis**:
|
||||
- Project Structure (feature-based vs flat)
|
||||
- Component Architecture (colocation, composition, size)
|
||||
- State Management (appropriate tools for each state type)
|
||||
- API Layer (centralized, type-safe patterns)
|
||||
- Testing Strategy (testing trophy compliance)
|
||||
- Styling Patterns (component libraries, utility CSS)
|
||||
- Error Handling (boundaries, interceptors, tracking)
|
||||
- Performance (code splitting, memoization, optimization)
|
||||
- Security (authentication, authorization, XSS prevention)
|
||||
- Standards Compliance (ESLint, TypeScript, naming conventions)
|
||||
- **Multiple Report Formats**: Markdown, JSON, migration roadmaps
|
||||
- **Prioritized Migration Plans**: P0-P3 severity with effort estimates
|
||||
- **ASCII Structure Diagrams**: Visual before/after comparisons
|
||||
- **Industry Standards**: Based on Bulletproof React best practices
|
||||
|
||||
## Installation
|
||||
|
||||
### Option 1: Claude Code (Recommended)
|
||||
|
||||
1. Clone or copy the `bulletproof-react-auditor` directory to your Claude skills directory
|
||||
2. Ensure Python 3.8+ is installed
|
||||
3. No additional dependencies required (uses Python standard library)
|
||||
|
||||
### Option 2: Manual Installation
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills
|
||||
git clone https://github.com/your-org/bulletproof-react-auditor.git
|
||||
```
|
||||
|
||||
## Usage with Claude Code
|
||||
|
||||
### Basic Audit
|
||||
|
||||
```
|
||||
Audit this React codebase using the bulletproof-react-auditor skill.
|
||||
```
|
||||
|
||||
### Structure-Focused Audit
|
||||
|
||||
```
|
||||
Run a structure audit on this React app against Bulletproof React patterns.
|
||||
```
|
||||
|
||||
### Generate Migration Plan
|
||||
|
||||
```
|
||||
Audit this React app and generate a migration plan to Bulletproof React architecture.
|
||||
```
|
||||
|
||||
### Custom Scope
|
||||
|
||||
```
|
||||
Audit this React codebase focusing on:
|
||||
- Project structure and feature organization
|
||||
- Component architecture patterns
|
||||
- State management approach
|
||||
```
|
||||
|
||||
## Direct Script Usage
|
||||
|
||||
```bash
|
||||
# Full audit with Markdown report
|
||||
python scripts/audit_engine.py /path/to/react-app --output audit.md
|
||||
|
||||
# Structure-focused audit
|
||||
python scripts/audit_engine.py /path/to/react-app --scope structure,components --output report.md
|
||||
|
||||
# Generate migration plan
|
||||
python scripts/audit_engine.py /path/to/react-app --migration-plan --output migration.md
|
||||
|
||||
# JSON output for CI/CD integration
|
||||
python scripts/audit_engine.py /path/to/react-app --format json --output audit.json
|
||||
|
||||
# Quick health check only (Phase 1)
|
||||
python scripts/audit_engine.py /path/to/react-app --phase quick
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
### Markdown (Default)
|
||||
|
||||
Human-readable report with:
|
||||
- ASCII structure diagrams (before/after)
|
||||
- Detailed findings with code examples
|
||||
- Step-by-step migration guidance
|
||||
- Suitable for PRs, documentation, team reviews
|
||||
|
||||
### JSON
|
||||
|
||||
Machine-readable format for CI/CD integration:
|
||||
```json
|
||||
{
|
||||
"summary": {
|
||||
"compliance_score": 72,
|
||||
"grade": "C",
|
||||
"critical_issues": 3,
|
||||
"migration_effort_days": 15
|
||||
},
|
||||
"findings": [...],
|
||||
"metrics": {...},
|
||||
"migration_plan": [...]
|
||||
}
|
||||
```
|
||||
|
||||
### Migration Plan
|
||||
|
||||
Prioritized roadmap with:
|
||||
- P0-P3 priority levels
|
||||
- Effort estimates per task
|
||||
- Dependency chains
|
||||
- Before/after code examples
|
||||
- ADR templates
|
||||
|
||||
## Audit Criteria
|
||||
|
||||
The skill audits based on 10 Bulletproof React categories:
|
||||
|
||||
### 1. Project Structure
|
||||
- Feature-based organization (80%+ code in features/)
|
||||
- Unidirectional dependencies (shared → features → app)
|
||||
- No cross-feature imports
|
||||
- Proper feature boundaries
|
||||
|
||||
### 2. Component Architecture
|
||||
- Component colocation (near usage)
|
||||
- Limited props (< 7-10 per component)
|
||||
- No large components (< 300 LOC)
|
||||
- No nested render functions
|
||||
- Proper abstraction (identify repetition first)
|
||||
|
||||
### 3. State Management
|
||||
- Appropriate tool for each state type
|
||||
- Local state preferred over global
|
||||
- Server cache separated (React Query/SWR)
|
||||
- Form state managed (React Hook Form)
|
||||
- URL state utilized
|
||||
|
||||
### 4. API Layer
|
||||
- Centralized API client
|
||||
- Type-safe request declarations
|
||||
- Colocated in features/
|
||||
- Data fetching hooks
|
||||
- Error handling
|
||||
|
||||
### 5. Testing Strategy
|
||||
- Testing trophy (70% integration, 20% unit, 10% E2E)
|
||||
- Semantic queries (getByRole preferred)
|
||||
- User behavior testing (not implementation)
|
||||
- 80%+ coverage on critical paths
|
||||
|
||||
### 6. Styling Patterns
|
||||
- Consistent approach (component library or utility CSS)
|
||||
- Colocated styles
|
||||
- Design system usage
|
||||
|
||||
### 7. Error Handling
|
||||
- API error interceptors
|
||||
- Multiple error boundaries
|
||||
- Error tracking service
|
||||
- User-friendly messages
|
||||
|
||||
### 8. Performance
|
||||
- Code splitting at routes
|
||||
- Memoization patterns
|
||||
- State localization
|
||||
- Image optimization
|
||||
- Bundle size monitoring
|
||||
|
||||
### 9. Security
|
||||
- JWT with HttpOnly cookies
|
||||
- Authorization (RBAC/PBAC)
|
||||
- Input sanitization
|
||||
- XSS prevention
|
||||
|
||||
### 10. Standards Compliance
|
||||
- ESLint configured
|
||||
- TypeScript strict mode
|
||||
- Prettier setup
|
||||
- Git hooks (Husky)
|
||||
- Absolute imports
|
||||
- Kebab-case naming
|
||||
|
||||
See [`reference/audit_criteria.md`](reference/audit_criteria.md) for complete checklist.
|
||||
|
||||
## Severity Levels
|
||||
|
||||
- **Critical (P0)**: Fix immediately (within 24 hours)
|
||||
- Security vulnerabilities, breaking architectural patterns
|
||||
|
||||
- **High (P1)**: Fix this sprint (within 2 weeks)
|
||||
- Major architectural violations, significant refactoring needed
|
||||
|
||||
- **Medium (P2)**: Fix next quarter (within 3 months)
|
||||
- Component design issues, state management improvements
|
||||
|
||||
- **Low (P3)**: Backlog
|
||||
- Styling consistency, minor optimizations
|
||||
|
||||
See [`reference/severity_matrix.md`](reference/severity_matrix.md) for detailed criteria.
|
||||
|
||||
## Migration Approach
|
||||
|
||||
### Phase 1: Foundation (Week 1-2)
|
||||
1. Create feature folders structure
|
||||
2. Move shared utilities to proper locations
|
||||
3. Set up absolute imports
|
||||
4. Configure ESLint for architecture rules
|
||||
|
||||
### Phase 2: Feature Extraction (Week 3-6)
|
||||
1. Identify feature boundaries
|
||||
2. Move components to features/
|
||||
3. Colocate API calls with features
|
||||
4. Extract feature-specific state
|
||||
|
||||
### Phase 3: Refinement (Week 7-10)
|
||||
1. Refactor large components
|
||||
2. Implement proper state management
|
||||
3. Add error boundaries
|
||||
4. Optimize performance
|
||||
|
||||
### Phase 4: Polish (Week 11-12)
|
||||
1. Improve test coverage
|
||||
2. Add documentation
|
||||
3. Implement remaining patterns
|
||||
4. Final review
|
||||
|
||||
## Examples
|
||||
|
||||
See the [`examples/`](examples/) directory for:
|
||||
- Sample audit report (React app before Bulletproof)
|
||||
- Complete migration plan with timeline
|
||||
- Before/after structure comparisons
|
||||
- Code transformation examples
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
bulletproof-react-auditor/
|
||||
├── SKILL.md # Skill definition (Claude loads this)
|
||||
├── README.md # This file
|
||||
├── scripts/
|
||||
│ ├── audit_engine.py # Core orchestrator
|
||||
│ ├── analyzers/ # Specialized analyzers
|
||||
│ │ ├── project_structure.py # Folder organization
|
||||
│ │ ├── component_architecture.py # Component patterns
|
||||
│ │ ├── state_management.py # State analysis
|
||||
│ │ ├── api_layer.py # API patterns
|
||||
│ │ ├── testing_strategy.py # Test quality
|
||||
│ │ ├── styling_patterns.py # Styling approach
|
||||
│ │ ├── error_handling.py # Error boundaries
|
||||
│ │ ├── performance_patterns.py # React performance
|
||||
│ │ ├── security_practices.py # React security
|
||||
│ │ └── standards_compliance.py # ESLint, TS, Prettier
|
||||
│ ├── report_generator.py # Multi-format reports
|
||||
│ └── migration_planner.py # Prioritized roadmaps
|
||||
├── reference/
|
||||
│ ├── bulletproof_principles.md # Complete BR guide
|
||||
│ ├── audit_criteria.md # Full checklist
|
||||
│ ├── severity_matrix.md # Issue prioritization
|
||||
│ └── migration_patterns.md # Common refactorings
|
||||
└── examples/
|
||||
├── sample_audit_report.md
|
||||
├── migration_plan.md
|
||||
└── before_after_structure.md
|
||||
```
|
||||
|
||||
## Extending the Skill
|
||||
|
||||
### Adding a New Analyzer
|
||||
|
||||
1. Create `scripts/analyzers/your_analyzer.py`
|
||||
2. Implement `analyze(codebase_path, metadata)` function
|
||||
3. Add to `ANALYZERS` dict in `audit_engine.py`
|
||||
|
||||
Example:
|
||||
```python
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze specific Bulletproof React pattern."""
|
||||
findings = []
|
||||
|
||||
# Your analysis logic here
|
||||
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'your_category',
|
||||
'title': 'Issue title',
|
||||
'current_state': 'What exists now',
|
||||
'target_state': 'Bulletproof recommendation',
|
||||
'migration_steps': ['Step 1', 'Step 2'],
|
||||
'effort': 'medium',
|
||||
})
|
||||
|
||||
return findings
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
### GitHub Actions Example
|
||||
|
||||
```yaml
|
||||
name: Bulletproof React Audit
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Run Bulletproof Audit
|
||||
run: |
|
||||
python bulletproof-react-auditor/scripts/audit_engine.py . \
|
||||
--format json \
|
||||
--output audit-report.json
|
||||
- name: Check Compliance Score
|
||||
run: |
|
||||
SCORE=$(jq '.summary.compliance_score' audit-report.json)
|
||||
if [ "$SCORE" -lt 70 ]; then
|
||||
echo "❌ Compliance score $SCORE below threshold (70)"
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Audit Before Major Refactoring**: Establish baseline before starting
|
||||
2. **Incremental Migration**: Don't refactor everything at once
|
||||
3. **Feature-by-Feature**: Migrate one feature at a time
|
||||
4. **Test Coverage First**: Ensure tests before restructuring
|
||||
5. **Team Alignment**: Share Bulletproof React principles with team
|
||||
6. **Document Decisions**: Create ADRs for architectural changes
|
||||
7. **Track Progress**: Re-run audits weekly to measure improvement
|
||||
|
||||
## Connor's Standards Integration
|
||||
|
||||
This skill enforces Connor's specific requirements:
|
||||
- **TypeScript Strict Mode**: No `any` types allowed
|
||||
- **Test Coverage**: 80%+ minimum on all code
|
||||
- **Testing Trophy**: 70% integration, 20% unit, 10% E2E
|
||||
- **Modern Testing**: Semantic queries (getByRole) preferred
|
||||
- **No Brittle Tests**: Avoid testing implementation details
|
||||
- **Code Quality**: No console.log, no `var`, strict equality
|
||||
- **Git Standards**: Conventional commits, proper branch naming
|
||||
|
||||
## Limitations
|
||||
|
||||
- Static analysis only (no runtime profiling)
|
||||
- React 16.8+ required (hooks-based)
|
||||
- Best suited for SPA/SSG patterns
|
||||
- Next.js apps may have additional patterns
|
||||
- Large codebases may need scoped analysis
|
||||
- Does not execute tests (analyzes test files)
|
||||
|
||||
## Version
|
||||
|
||||
**1.0.0** - Initial release
|
||||
|
||||
## Standards Compliance
|
||||
|
||||
Based on:
|
||||
- Bulletproof React Official Guide
|
||||
- Kent C. Dodds Testing Trophy
|
||||
- React Best Practices 2024-25
|
||||
- TypeScript Strict Mode Guidelines
|
||||
- Connor's Development Standards
|
||||
|
||||
## License
|
||||
|
||||
Apache 2.0 (example skill for demonstration)
|
||||
|
||||
---
|
||||
|
||||
**Built with**: Python 3.8+
|
||||
**Anthropic Skill Version**: 1.0
|
||||
**Last Updated**: 2024-10-25
|
||||
**Bulletproof React Version**: Based on v2024 guidelines
|
||||
130
skills/bulletproof-react-auditor/SKILL.md
Normal file
130
skills/bulletproof-react-auditor/SKILL.md
Normal file
@@ -0,0 +1,130 @@
|
||||
---
|
||||
name: bulletproof-react-auditor
|
||||
description: Use PROACTIVELY when users ask about React project structure, Bulletproof React patterns, or need architecture guidance. Covers structure setup, codebase auditing, anti-pattern detection, and feature-based migration planning. Triggers on "bulletproof react", "React structure help", "organize React app", or "audit my architecture".
|
||||
---
|
||||
|
||||
# Bulletproof React Auditor
|
||||
|
||||
Audits React/TypeScript codebases against Bulletproof React architecture with migration planning.
|
||||
|
||||
## When to Use
|
||||
|
||||
**Natural Language Triggers** (semantic matching, not keywords):
|
||||
- Questions about React project structure or organization
|
||||
- Mentions of "bulletproof react" or feature-based architecture
|
||||
- Requests to audit, review, or improve React codebase
|
||||
- Planning migrations or refactoring React applications
|
||||
- Seeking guidance on component patterns or folder structure
|
||||
|
||||
**Use Cases**:
|
||||
- Setting up new React project structure
|
||||
- Reorganizing existing flat codebase
|
||||
- Auditing architecture against Bulletproof standards
|
||||
- Planning migration to feature-based patterns
|
||||
- Code review for structural anti-patterns
|
||||
- Generating refactoring guidance and ADRs
|
||||
|
||||
## Bulletproof Structure Target
|
||||
|
||||
```
|
||||
src/
|
||||
├── app/ # Routes, providers
|
||||
├── components/ # Shared components ONLY
|
||||
├── config/ # Global config
|
||||
├── features/ # Feature modules (most code)
|
||||
│ └── feature/
|
||||
│ ├── api/
|
||||
│ ├── components/
|
||||
│ ├── hooks/
|
||||
│ ├── stores/
|
||||
│ └── types/
|
||||
├── hooks/ # Shared hooks
|
||||
├── lib/ # Third-party configs
|
||||
├── stores/ # Global state
|
||||
├── testing/ # Test utilities
|
||||
├── types/ # Shared types
|
||||
└── utils/ # Shared utilities
|
||||
```
|
||||
|
||||
## Audit Categories
|
||||
|
||||
| Category | Key Checks |
|
||||
|----------|------------|
|
||||
| Structure | Feature folders, cross-feature imports, boundaries |
|
||||
| Components | Size (<300 LOC), props (<10), composition |
|
||||
| State | Appropriate categories, localization, server cache |
|
||||
| API Layer | Centralized client, types, React Query/SWR |
|
||||
| Testing | Trophy (70/20/10), semantic queries, behavior |
|
||||
| Styling | Consistent approach, component library |
|
||||
| Errors | Boundaries, interceptors, tracking |
|
||||
| Performance | Code splitting, memoization, bundle size |
|
||||
| Security | JWT cookies, RBAC, XSS prevention |
|
||||
| Standards | ESLint, Prettier, TS strict, Husky |
|
||||
|
||||
## Usage Examples
|
||||
|
||||
```
|
||||
# Basic audit
|
||||
Audit this React codebase using bulletproof-react-auditor.
|
||||
|
||||
# Structure focus
|
||||
Run structure audit against Bulletproof React patterns.
|
||||
|
||||
# Migration plan
|
||||
Generate migration plan to Bulletproof architecture.
|
||||
|
||||
# Custom scope
|
||||
Audit focusing on structure, components, and state management.
|
||||
```
|
||||
|
||||
## Output Formats
|
||||
|
||||
1. **Markdown Report** - ASCII diagrams, code examples
|
||||
2. **JSON Report** - Machine-readable for CI/CD
|
||||
3. **Migration Plan** - Roadmap with effort estimates
|
||||
|
||||
## Priority Levels
|
||||
|
||||
| Priority | Examples | Timeline |
|
||||
|----------|----------|----------|
|
||||
| P0 Critical | Security vulns, breaking issues | Immediate |
|
||||
| P1 High | Feature folder creation, reorg | This sprint |
|
||||
| P2 Medium | State refactor, API layer | Next quarter |
|
||||
| P3 Low | Styling, docs, polish | Backlog |
|
||||
|
||||
## Connor's Standards Enforced
|
||||
|
||||
- TypeScript strict mode (no `any`)
|
||||
- 80%+ test coverage
|
||||
- Testing trophy: 70% integration, 20% unit, 10% E2E
|
||||
- No console.log in production
|
||||
- Semantic queries (getByRole preferred)
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Fix folder organization before component refactoring
|
||||
2. Extract features before other changes
|
||||
3. Maintain test coverage during migration
|
||||
4. Incremental migration, not all at once
|
||||
5. Document decisions with ADRs
|
||||
|
||||
## Limitations
|
||||
|
||||
- Static analysis only
|
||||
- Requires React 16.8+ (hooks)
|
||||
- Best for SPA/SSG (Next.js differs)
|
||||
- Large codebases need scoped analysis
|
||||
|
||||
## Resources
|
||||
|
||||
- [Bulletproof React Guide](https://github.com/alan2207/bulletproof-react)
|
||||
- [Project Structure](https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md)
|
||||
- [Sample App](https://github.com/alan2207/bulletproof-react/tree/master/apps/react-vite)
|
||||
|
||||
## References
|
||||
|
||||
See `reference/` for:
|
||||
- Complete Bulletproof principles guide
|
||||
- Detailed audit criteria checklist
|
||||
- Migration patterns and examples
|
||||
- ADR templates
|
||||
353
skills/bulletproof-react-auditor/examples/sample_audit_report.md
Normal file
353
skills/bulletproof-react-auditor/examples/sample_audit_report.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# Bulletproof React Audit Report
|
||||
|
||||
**Generated**: 2024-10-25 15:30:00
|
||||
**Codebase**: `/Users/developer/projects/my-react-app`
|
||||
**Tech Stack**: React, TypeScript, Vite, Redux, Jest
|
||||
**Structure Type**: Flat
|
||||
**Total Files**: 287
|
||||
**Lines of Code**: 18,420
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
### Overall Bulletproof Compliance: **62/100** (Grade: D)
|
||||
|
||||
### Category Scores
|
||||
|
||||
- **Structure**: 45/100 ⚠️ (Needs major refactoring)
|
||||
- **Components**: 68/100 ⚠️ (Some improvements needed)
|
||||
- **State Management**: 55/100 ⚠️ (Missing server cache)
|
||||
- **API Layer**: 50/100 ⚠️ (Scattered fetch calls)
|
||||
- **Testing**: 72/100 ⚠️ (Below 80% coverage)
|
||||
- **Styling**: 80/100 ✅ (Good - using Tailwind)
|
||||
- **Error Handling**: 40/100 ⚠️ (Missing error boundaries)
|
||||
- **Performance**: 65/100 ⚠️ (No code splitting)
|
||||
- **Security**: 58/100 ⚠️ (Tokens in localStorage)
|
||||
- **Standards**: 85/100 ✅ (Good compliance)
|
||||
|
||||
### Issue Summary
|
||||
|
||||
- **Critical Issues**: 3
|
||||
- **High Issues**: 12
|
||||
- **Medium Issues**: 24
|
||||
- **Low Issues**: 8
|
||||
- **Total Issues**: 47
|
||||
|
||||
**Estimated Migration Effort**: 18.5 person-days (~4 weeks for 1 developer)
|
||||
|
||||
---
|
||||
|
||||
## Detailed Findings
|
||||
|
||||
### 🚨 CRITICAL (3 issues)
|
||||
|
||||
#### 1. Tokens stored in localStorage (Security)
|
||||
|
||||
**Current State**: Authentication tokens stored in localStorage in 3 files
|
||||
**Target State**: Use HttpOnly cookies for JWT storage
|
||||
|
||||
**Files Affected**:
|
||||
- `src/utils/auth.ts`
|
||||
- `src/hooks/useAuth.ts`
|
||||
- `src/api/client.ts`
|
||||
|
||||
**Impact**: localStorage is vulnerable to XSS attacks. If attacker injects JavaScript, they can steal authentication tokens.
|
||||
|
||||
**Migration Steps**:
|
||||
1. Configure API backend to set JWT in HttpOnly cookie
|
||||
2. Remove `localStorage.setItem('token', ...)` calls
|
||||
3. Use `credentials: 'include'` in fetch requests
|
||||
4. Implement CSRF protection
|
||||
5. Test authentication flow
|
||||
|
||||
**Effort**: MEDIUM
|
||||
|
||||
---
|
||||
|
||||
#### 2. No features/ directory - flat structure (Structure)
|
||||
|
||||
**Current State**: All 287 files in flat src/ directory structure
|
||||
**Target State**: 80%+ code organized in feature-based modules
|
||||
|
||||
**Impact**:
|
||||
- Difficult to scale beyond current size
|
||||
- No clear feature boundaries
|
||||
- High coupling between unrelated code
|
||||
- Difficult to test in isolation
|
||||
- New developers struggle to find code
|
||||
|
||||
**Migration Steps**:
|
||||
1. Create `src/features/` directory
|
||||
2. Identify distinct features (e.g., authentication, dashboard, profile, settings)
|
||||
3. Create directories for each feature
|
||||
4. Move feature-specific code to respective features/
|
||||
5. Organize each feature with api/, components/, hooks/, stores/ subdirectories
|
||||
6. Update all import paths
|
||||
7. Test thoroughly after each feature migration
|
||||
|
||||
**Effort**: HIGH (plan for 2 weeks)
|
||||
|
||||
---
|
||||
|
||||
#### 3. No testing framework detected (Testing)
|
||||
|
||||
**Current State**: Jest found but no @testing-library/react
|
||||
**Target State**: Use Testing Library for user-centric React testing
|
||||
|
||||
**Impact**:
|
||||
- Testing components requires low-level implementation testing
|
||||
- Tests are brittle and break on refactoring
|
||||
- Cannot follow testing trophy distribution
|
||||
- Poor test quality
|
||||
|
||||
**Migration Steps**:
|
||||
1. Install @testing-library/react
|
||||
2. Install @testing-library/jest-dom
|
||||
3. Configure test setup file
|
||||
4. Write example tests using Testing Library patterns
|
||||
5. Train team on Testing Library principles
|
||||
|
||||
**Effort**: LOW
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ HIGH (12 issues - showing top 5)
|
||||
|
||||
#### 4. No data fetching library (State Management)
|
||||
|
||||
**Current State**: Manual API state management with Redux
|
||||
**Target State**: Use React Query or SWR for server cache state
|
||||
|
||||
**Migration Steps**:
|
||||
1. Install @tanstack/react-query
|
||||
2. Wrap app with QueryClientProvider
|
||||
3. Convert Redux API slices to React Query hooks
|
||||
4. Remove manual loading/error state management
|
||||
5. Configure caching strategies
|
||||
|
||||
**Effort**: MEDIUM
|
||||
|
||||
---
|
||||
|
||||
#### 5. Test coverage at 65.3% (Testing)
|
||||
|
||||
**Current State**: Line coverage: 65.3%, Branch coverage: 58.2%
|
||||
**Target State**: Maintain 80%+ test coverage
|
||||
|
||||
**Critical Untested Paths**:
|
||||
- Authentication flow
|
||||
- Payment processing
|
||||
- User profile updates
|
||||
|
||||
**Migration Steps**:
|
||||
1. Generate coverage report with uncovered files
|
||||
2. Prioritize critical paths (authentication, payments)
|
||||
3. Write integration tests first (70% of tests)
|
||||
4. Add unit tests for business logic
|
||||
5. Configure coverage thresholds in jest.config.js
|
||||
|
||||
**Effort**: HIGH
|
||||
|
||||
---
|
||||
|
||||
#### 6. Large component: UserDashboard.tsx (468 LOC) (Components)
|
||||
|
||||
**Current State**: `src/components/UserDashboard.tsx` has 468 lines
|
||||
**Target State**: Components should be < 300 lines
|
||||
|
||||
**Migration Steps**:
|
||||
1. Identify distinct UI sections in dashboard
|
||||
2. Extract sections to separate components (DashboardHeader, DashboardStats, DashboardActivity)
|
||||
3. Move business logic to custom hooks (useDashboardData)
|
||||
4. Extract complex calculations to utility functions
|
||||
5. Update tests to test new components independently
|
||||
|
||||
**Effort**: MEDIUM
|
||||
|
||||
---
|
||||
|
||||
#### 7. Cross-feature imports detected (Structure)
|
||||
|
||||
**Current State**: 8 files import from other features
|
||||
**Violations**:
|
||||
- `features/dashboard → features/profile`
|
||||
- `features/settings → features/authentication`
|
||||
|
||||
**Target State**: Features should be independent. Shared code belongs in src/components/ or src/utils/
|
||||
|
||||
**Migration Steps**:
|
||||
1. Identify shared code being imported across features
|
||||
2. Move truly shared components to src/components/
|
||||
3. Move shared utilities to src/utils/
|
||||
4. If code is feature-specific, duplicate it or refactor feature boundaries
|
||||
|
||||
**Effort**: MEDIUM
|
||||
|
||||
---
|
||||
|
||||
#### 8. No error boundaries detected (Error Handling)
|
||||
|
||||
**Current State**: No ErrorBoundary components found
|
||||
**Target State**: Multiple error boundaries at route and feature levels
|
||||
|
||||
**Migration Steps**:
|
||||
1. Create src/components/ErrorBoundary.tsx
|
||||
2. Wrap each route with ErrorBoundary
|
||||
3. Add feature-level error boundaries
|
||||
4. Display user-friendly error messages
|
||||
5. Log errors to Sentry
|
||||
|
||||
**Effort**: LOW
|
||||
|
||||
---
|
||||
|
||||
### 📊 MEDIUM (24 issues - showing top 3)
|
||||
|
||||
#### 9. Too many shared components (Structure)
|
||||
|
||||
**Current State**: 62.3% of components in src/components/ (shared)
|
||||
**Target State**: Most components should be feature-specific
|
||||
|
||||
**Migration Steps**:
|
||||
1. Review each shared component
|
||||
2. Identify components used by only one feature
|
||||
3. Move feature-specific components to their features
|
||||
4. Keep only truly shared components in src/components/
|
||||
|
||||
**Effort**: MEDIUM
|
||||
|
||||
---
|
||||
|
||||
#### 10. Component with 12 props: UserProfileForm (Components)
|
||||
|
||||
**Current State**: `UserProfileForm` accepts 12 props
|
||||
**Target State**: Components should accept < 7-10 props
|
||||
|
||||
**Migration Steps**:
|
||||
1. Group related props into configuration object
|
||||
2. Use composition (children) instead of render props
|
||||
3. Extract sub-components with their own props
|
||||
4. Consider Context for deeply shared state
|
||||
|
||||
**Effort**: LOW
|
||||
|
||||
---
|
||||
|
||||
#### 11. No code splitting detected (Performance)
|
||||
|
||||
**Current State**: No React.lazy() usage found
|
||||
**Target State**: Use code splitting for routes and large components
|
||||
|
||||
**Migration Steps**:
|
||||
1. Wrap route components with React.lazy()
|
||||
2. Add Suspense boundaries with loading states
|
||||
3. Split large features into separate chunks
|
||||
4. Analyze bundle size with vite-bundle-analyzer
|
||||
|
||||
**Effort**: LOW
|
||||
|
||||
---
|
||||
|
||||
## Recommendations
|
||||
|
||||
### Immediate Action Required (This Week)
|
||||
|
||||
1. **Security**: Move tokens from localStorage to HttpOnly cookies
|
||||
2. **Structure**: Create features/ directory and plan migration
|
||||
3. **Testing**: Install Testing Library and write example tests
|
||||
|
||||
### This Sprint (Next 2 Weeks)
|
||||
|
||||
4. **Structure**: Begin feature extraction (start with 1-2 features)
|
||||
5. **State**: Add React Query for API calls
|
||||
6. **Testing**: Increase coverage to 70%+
|
||||
7. **Components**: Refactor largest components (> 400 LOC)
|
||||
8. **Errors**: Add error boundaries
|
||||
|
||||
### Next Quarter (3 Months)
|
||||
|
||||
9. **Structure**: Complete feature-based migration
|
||||
10. **Testing**: Achieve 80%+ coverage
|
||||
11. **Performance**: Implement code splitting
|
||||
12. **State**: Evaluate Redux necessity (might not need with React Query)
|
||||
|
||||
### Backlog
|
||||
|
||||
13. **Standards**: Add git hooks (Husky) for pre-commit checks
|
||||
14. **Components**: Improve component colocation
|
||||
15. **Styling**: Document design system
|
||||
16. **Naming**: Enforce kebab-case file naming
|
||||
|
||||
---
|
||||
|
||||
## Migration Priority Roadmap
|
||||
|
||||
### Week 1-2: Foundation
|
||||
- [ ] Fix security issues (localStorage tokens)
|
||||
- [ ] Create features/ structure
|
||||
- [ ] Install Testing Library
|
||||
- [ ] Add error boundaries
|
||||
- [ ] Configure React Query
|
||||
|
||||
### Week 3-4: Feature Extraction Phase 1
|
||||
- [ ] Extract authentication feature
|
||||
- [ ] Extract dashboard feature
|
||||
- [ ] Update imports and test
|
||||
- [ ] Improve test coverage to 70%
|
||||
|
||||
### Week 5-8: Feature Extraction Phase 2
|
||||
- [ ] Extract remaining features
|
||||
- [ ] Refactor large components
|
||||
- [ ] Add comprehensive error handling
|
||||
- [ ] Achieve 80%+ test coverage
|
||||
|
||||
### Week 9-12: Optimization
|
||||
- [ ] Implement code splitting
|
||||
- [ ] Performance optimizations
|
||||
- [ ] Security hardening
|
||||
- [ ] Documentation updates
|
||||
|
||||
---
|
||||
|
||||
## Architecture Comparison
|
||||
|
||||
### Current Structure
|
||||
```
|
||||
src/
|
||||
├── components/ (180 components - too many!)
|
||||
├── hooks/ (12 hooks)
|
||||
├── utils/ (15 utility files)
|
||||
├── store/ (Redux slices)
|
||||
├── api/ (API calls)
|
||||
└── pages/ (Route components)
|
||||
```
|
||||
|
||||
### Target Bulletproof Structure
|
||||
```
|
||||
src/
|
||||
├── app/
|
||||
│ ├── routes/
|
||||
│ ├── app.tsx
|
||||
│ └── provider.tsx
|
||||
├── features/
|
||||
│ ├── authentication/
|
||||
│ │ ├── api/
|
||||
│ │ ├── components/
|
||||
│ │ ├── hooks/
|
||||
│ │ └── stores/
|
||||
│ ├── dashboard/
|
||||
│ │ └── ...
|
||||
│ └── profile/
|
||||
│ └── ...
|
||||
├── components/ (Only truly shared - ~20 components)
|
||||
├── hooks/ (Shared hooks)
|
||||
├── lib/ (API client, configs)
|
||||
├── utils/ (Shared utilities)
|
||||
└── types/ (Shared types)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Report generated by Bulletproof React Auditor Skill v1.0*
|
||||
*Based on Bulletproof React principles and Connor's development standards*
|
||||
250
skills/bulletproof-react-auditor/reference/audit_criteria.md
Normal file
250
skills/bulletproof-react-auditor/reference/audit_criteria.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Bulletproof React Audit Criteria
|
||||
|
||||
Complete checklist for auditing React/TypeScript applications against Bulletproof React architecture principles.
|
||||
|
||||
## 1. Project Structure
|
||||
|
||||
### Feature-Based Organization
|
||||
- [ ] 80%+ of code organized in src/features/
|
||||
- [ ] Each feature has its own directory
|
||||
- [ ] Features are independent (no cross-feature imports)
|
||||
- [ ] Feature subdirectories: api/, components/, hooks/, stores/, types/, utils/
|
||||
|
||||
### Top-Level Directories
|
||||
- [ ] src/app/ exists (application layer)
|
||||
- [ ] src/features/ exists and contains features
|
||||
- [ ] src/components/ for truly shared components only
|
||||
- [ ] src/hooks/ for shared custom hooks
|
||||
- [ ] src/lib/ for third-party configurations
|
||||
- [ ] src/utils/ for shared utilities
|
||||
- [ ] src/types/ for shared TypeScript types
|
||||
- [ ] src/stores/ for global state (if needed)
|
||||
|
||||
### Unidirectional Dependencies
|
||||
- [ ] No cross-feature imports
|
||||
- [ ] Shared code imported into features (not vice versa)
|
||||
- [ ] App layer imports from features
|
||||
- [ ] Clean dependency flow: shared → features → app
|
||||
|
||||
## 2. Component Architecture
|
||||
|
||||
### Component Design
|
||||
- [ ] Components < 300 lines of code
|
||||
- [ ] No large components (> 500 LOC)
|
||||
- [ ] Components accept < 7-10 props
|
||||
- [ ] No nested render functions
|
||||
- [ ] Component colocation (near where used)
|
||||
- [ ] Proper use of composition over excessive props
|
||||
|
||||
### File Organization
|
||||
- [ ] Kebab-case file naming
|
||||
- [ ] Components colocated with tests
|
||||
- [ ] Styles colocated with components
|
||||
- [ ] Feature-specific components in features/
|
||||
- [ ] Only truly shared components in src/components/
|
||||
|
||||
### Abstraction
|
||||
- [ ] No premature abstractions
|
||||
- [ ] Repetition identified before creating abstractions
|
||||
- [ ] Components are focused and single-purpose
|
||||
|
||||
## 3. State Management
|
||||
|
||||
### State Categories
|
||||
- [ ] Component state with useState/useReducer
|
||||
- [ ] Global state with Context, Zustand, or Jotai
|
||||
- [ ] Server cache state with React Query or SWR
|
||||
- [ ] Form state with React Hook Form or Formik
|
||||
- [ ] URL state with React Router
|
||||
|
||||
### State Localization
|
||||
- [ ] State as local as possible
|
||||
- [ ] Global state only when necessary
|
||||
- [ ] No single massive global state object
|
||||
- [ ] Context split into multiple focused providers
|
||||
|
||||
### Server State
|
||||
- [ ] React Query or SWR for API data
|
||||
- [ ] Proper caching configuration
|
||||
- [ ] No manual loading/error state for API calls
|
||||
- [ ] Optimistic updates where appropriate
|
||||
|
||||
## 4. API Layer
|
||||
|
||||
### Centralized Configuration
|
||||
- [ ] Single API client instance
|
||||
- [ ] Configured in src/lib/
|
||||
- [ ] Base URL configuration
|
||||
- [ ] Request/response interceptors
|
||||
- [ ] Error handling interceptors
|
||||
|
||||
### Request Organization
|
||||
- [ ] API calls colocated in features/*/api/
|
||||
- [ ] Type-safe request declarations
|
||||
- [ ] Custom hooks for each endpoint
|
||||
- [ ] Validation schemas with types
|
||||
- [ ] Proper error handling
|
||||
|
||||
## 5. Testing Strategy
|
||||
|
||||
### Coverage
|
||||
- [ ] 80%+ line coverage
|
||||
- [ ] 75%+ branch coverage
|
||||
- [ ] 100% coverage on critical paths
|
||||
- [ ] Coverage reports generated
|
||||
|
||||
### Testing Trophy Distribution
|
||||
- [ ] ~70% integration tests
|
||||
- [ ] ~20% unit tests
|
||||
- [ ] ~10% E2E tests
|
||||
|
||||
### Test Quality
|
||||
- [ ] Tests named "should X when Y"
|
||||
- [ ] Semantic queries (getByRole, getByLabelText)
|
||||
- [ ] Testing user behavior, not implementation
|
||||
- [ ] No brittle tests (exact counts, element ordering)
|
||||
- [ ] Tests isolated and independent
|
||||
- [ ] No flaky tests
|
||||
|
||||
### Testing Tools
|
||||
- [ ] Vitest or Jest configured
|
||||
- [ ] @testing-library/react installed
|
||||
- [ ] @testing-library/jest-dom for assertions
|
||||
- [ ] Playwright or Cypress for E2E (optional)
|
||||
|
||||
## 6. Styling Patterns
|
||||
|
||||
### Styling Approach
|
||||
- [ ] Consistent styling method chosen
|
||||
- [ ] Component library (Chakra, Radix, MUI) OR
|
||||
- [ ] Utility CSS (Tailwind) OR
|
||||
- [ ] CSS-in-JS (Emotion, styled-components)
|
||||
- [ ] Styles colocated with components
|
||||
|
||||
### Design System
|
||||
- [ ] Design tokens defined
|
||||
- [ ] Color palette established
|
||||
- [ ] Typography scale defined
|
||||
- [ ] Spacing system consistent
|
||||
|
||||
## 7. Error Handling
|
||||
|
||||
### Error Boundaries
|
||||
- [ ] Multiple error boundaries at strategic locations
|
||||
- [ ] Route-level error boundaries
|
||||
- [ ] Feature-level error boundaries
|
||||
- [ ] User-friendly error messages
|
||||
- [ ] Error recovery mechanisms
|
||||
|
||||
### API Errors
|
||||
- [ ] API error interceptors configured
|
||||
- [ ] User notifications for errors
|
||||
- [ ] Automatic retry logic where appropriate
|
||||
- [ ] Unauthorized user logout
|
||||
|
||||
### Error Tracking
|
||||
- [ ] Sentry or similar service configured
|
||||
- [ ] User context added to errors
|
||||
- [ ] Environment-specific error handling
|
||||
- [ ] Source maps configured for production
|
||||
|
||||
## 8. Performance
|
||||
|
||||
### Code Splitting
|
||||
- [ ] React.lazy() for route components
|
||||
- [ ] Suspense boundaries with loading states
|
||||
- [ ] Large features split into chunks
|
||||
- [ ] Bundle size monitored and optimized
|
||||
|
||||
### React Performance
|
||||
- [ ] State localized to prevent re-renders
|
||||
- [ ] React.memo for expensive components
|
||||
- [ ] useMemo for expensive calculations
|
||||
- [ ] useCallback for stable function references
|
||||
- [ ] Children prop optimization patterns
|
||||
|
||||
### Asset Optimization
|
||||
- [ ] Images lazy loaded
|
||||
- [ ] Images in modern formats (WebP)
|
||||
- [ ] Responsive images with srcset
|
||||
- [ ] Images < 500KB
|
||||
- [ ] Videos lazy loaded or streamed
|
||||
|
||||
## 9. Security
|
||||
|
||||
### Authentication
|
||||
- [ ] JWT stored in HttpOnly cookies (not localStorage)
|
||||
- [ ] Secure session management
|
||||
- [ ] Token refresh logic
|
||||
- [ ] Logout functionality
|
||||
|
||||
### Authorization
|
||||
- [ ] RBAC or PBAC implemented
|
||||
- [ ] Protected routes
|
||||
- [ ] Permission checks on actions
|
||||
- [ ] API-level authorization
|
||||
|
||||
### XSS Prevention
|
||||
- [ ] Input sanitization (DOMPurify)
|
||||
- [ ] No dangerouslySetInnerHTML without sanitization
|
||||
- [ ] Output encoding
|
||||
- [ ] Content Security Policy
|
||||
|
||||
### CSRF Protection
|
||||
- [ ] CSRF tokens for state-changing requests
|
||||
- [ ] SameSite cookie attribute
|
||||
- [ ] Verify origin headers
|
||||
|
||||
## 10. Standards Compliance
|
||||
|
||||
### ESLint
|
||||
- [ ] .eslintrc or eslint.config.js configured
|
||||
- [ ] React rules enabled
|
||||
- [ ] TypeScript rules enabled
|
||||
- [ ] Accessibility rules (jsx-a11y)
|
||||
- [ ] Architectural rules (import restrictions)
|
||||
|
||||
### TypeScript
|
||||
- [ ] strict: true in tsconfig.json
|
||||
- [ ] No `any` types
|
||||
- [ ] Explicit return types
|
||||
- [ ] Type definitions for third-party libraries
|
||||
- [ ] Types colocated with features
|
||||
|
||||
### Prettier
|
||||
- [ ] Prettier configured
|
||||
- [ ] Format on save enabled
|
||||
- [ ] Consistent code style
|
||||
- [ ] .prettierrc configuration
|
||||
|
||||
### Git Hooks
|
||||
- [ ] Husky configured
|
||||
- [ ] Pre-commit linting
|
||||
- [ ] Pre-commit type checking
|
||||
- [ ] Pre-commit tests (optional)
|
||||
|
||||
### File Naming
|
||||
- [ ] Kebab-case for files and directories
|
||||
- [ ] Consistent naming conventions
|
||||
- [ ] ESLint rule to enforce naming
|
||||
|
||||
### Absolute Imports
|
||||
- [ ] TypeScript paths configured (@/ prefix)
|
||||
- [ ] Imports use @/ instead of relative paths
|
||||
- [ ] Easier refactoring and moving files
|
||||
|
||||
## Compliance Scoring
|
||||
|
||||
### Grade Scale
|
||||
- **A (90-100)**: Excellent Bulletproof React compliance
|
||||
- **B (80-89)**: Good compliance, minor improvements needed
|
||||
- **C (70-79)**: Moderate compliance, significant refactoring recommended
|
||||
- **D (60-69)**: Poor compliance, major architectural changes needed
|
||||
- **F (<60)**: Non-compliant, complete restructuring required
|
||||
|
||||
### Category Weights
|
||||
All categories weighted equally for overall score.
|
||||
|
||||
---
|
||||
|
||||
**Note**: This checklist represents the ideal Bulletproof React architecture. Adapt based on your project's specific needs and constraints while maintaining the core principles.
|
||||
248
skills/bulletproof-react-auditor/reference/severity_matrix.md
Normal file
248
skills/bulletproof-react-auditor/reference/severity_matrix.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Severity Matrix
|
||||
|
||||
Priority levels and response times for Bulletproof React audit findings.
|
||||
|
||||
## Severity Levels
|
||||
|
||||
### Critical (P0)
|
||||
**Fix immediately (within 24 hours)**
|
||||
|
||||
#### Criteria
|
||||
- Security vulnerabilities (tokens in localStorage, XSS risks)
|
||||
- Breaking architectural violations that prevent scalability
|
||||
- No testing framework in production app
|
||||
- TypeScript strict mode disabled with widespread `any` usage
|
||||
|
||||
#### Examples
|
||||
- Authentication tokens stored in localStorage
|
||||
- No error boundaries in production app
|
||||
- Zero test coverage on critical paths
|
||||
- Multiple cross-feature dependencies creating circular imports
|
||||
|
||||
#### Impact
|
||||
- Security breaches possible
|
||||
- Application instability
|
||||
- Cannot safely refactor or add features
|
||||
- Technical debt compounds rapidly
|
||||
|
||||
---
|
||||
|
||||
### High (P1)
|
||||
**Fix this sprint (within 2 weeks)**
|
||||
|
||||
#### Criteria
|
||||
- Major architectural misalignment with Bulletproof React
|
||||
- No data fetching library (manual API state management)
|
||||
- Test coverage < 80%
|
||||
- Large components (> 400 LOC) with multiple responsibilities
|
||||
- No features/ directory with >50 components
|
||||
|
||||
#### Examples
|
||||
- Flat structure instead of feature-based
|
||||
- Scattered fetch calls throughout components
|
||||
- No React Query/SWR for server state
|
||||
- Components with 15+ props
|
||||
- No error tracking service (Sentry)
|
||||
|
||||
#### Impact
|
||||
- Difficult to maintain and extend
|
||||
- Poor developer experience
|
||||
- Slow feature development
|
||||
- Bugs hard to track and fix
|
||||
- Testing becomes increasingly difficult
|
||||
|
||||
---
|
||||
|
||||
### Medium (P2)
|
||||
**Fix next quarter (within 3 months)**
|
||||
|
||||
#### Criteria
|
||||
- Component design anti-patterns
|
||||
- State management could be improved
|
||||
- Missing recommended directories
|
||||
- Some cross-feature imports
|
||||
- No code splitting
|
||||
- Inconsistent styling approaches
|
||||
|
||||
#### Examples
|
||||
- Components 200-400 LOC
|
||||
- Context with 5+ state values
|
||||
- Too many shared components (should be feature-specific)
|
||||
- Nested render functions instead of components
|
||||
- Multiple styling systems in use
|
||||
- Large images not optimized
|
||||
|
||||
#### Impact
|
||||
- Code is maintainable but could be better
|
||||
- Some technical debt accumulating
|
||||
- Refactoring is more difficult than it should be
|
||||
- Performance could be better
|
||||
- Developer onboarding takes longer
|
||||
|
||||
---
|
||||
|
||||
### Low (P3)
|
||||
**Backlog (schedule when convenient)**
|
||||
|
||||
#### Criteria
|
||||
- Minor deviations from Bulletproof React patterns
|
||||
- Stylistic improvements
|
||||
- Missing nice-to-have features
|
||||
- Small optimizations
|
||||
|
||||
#### Examples
|
||||
- Files not using kebab-case naming
|
||||
- No Prettier configured
|
||||
- No git hooks (Husky)
|
||||
- Missing some recommended directories
|
||||
- Test naming doesn't follow "should X when Y"
|
||||
- Some components could be better colocated
|
||||
|
||||
#### Impact
|
||||
- Minimal impact on development
|
||||
- Minor inconsistencies
|
||||
- Small developer experience improvements possible
|
||||
- Low-priority technical debt
|
||||
|
||||
---
|
||||
|
||||
## Effort Estimation
|
||||
|
||||
### Low Effort (< 1 day)
|
||||
- Installing dependencies
|
||||
- Creating configuration files
|
||||
- Renaming files
|
||||
- Adding error boundaries
|
||||
- Setting up Prettier/ESLint
|
||||
- Configuring git hooks
|
||||
|
||||
### Medium Effort (1-5 days)
|
||||
- Creating features/ structure
|
||||
- Organizing existing code into features
|
||||
- Refactoring large components
|
||||
- Adding React Query/SWR
|
||||
- Setting up comprehensive error handling
|
||||
- Improving test coverage to 80%
|
||||
|
||||
### High Effort (1-3 weeks)
|
||||
- Complete architecture restructuring
|
||||
- Migrating from flat to feature-based structure
|
||||
- Comprehensive security improvements
|
||||
- Building out full test suite
|
||||
- Large-scale refactoring
|
||||
- Multiple concurrent improvements
|
||||
|
||||
---
|
||||
|
||||
## Priority Decision Matrix
|
||||
|
||||
| Severity | Effort Low | Effort Medium | Effort High |
|
||||
|----------|------------|---------------|-------------|
|
||||
| **Critical** | P0 - Do Now | P0 - Do Now | P0 - Plan & Start |
|
||||
| **High** | P1 - This Sprint | P1 - This Sprint | P1 - This Quarter |
|
||||
| **Medium** | P2 - Next Sprint | P2 - Next Quarter | P2 - This Year |
|
||||
| **Low** | P3 - Backlog | P3 - Backlog | P3 - Nice to Have |
|
||||
|
||||
---
|
||||
|
||||
## Response Time Guidelines
|
||||
|
||||
### Critical (P0)
|
||||
- **Notification**: Immediate (Slack/email alert)
|
||||
- **Acknowledgment**: Within 1 hour
|
||||
- **Plan**: Within 4 hours
|
||||
- **Fix**: Within 24 hours
|
||||
- **Verification**: Immediately after fix
|
||||
- **Documentation**: ADR created
|
||||
|
||||
### High (P1)
|
||||
- **Notification**: Within 1 day
|
||||
- **Acknowledgment**: Within 1 day
|
||||
- **Plan**: Within 2 days
|
||||
- **Fix**: Within current sprint (2 weeks)
|
||||
- **Verification**: Before sprint end
|
||||
- **Documentation**: Updated in sprint retrospective
|
||||
|
||||
### Medium (P2)
|
||||
- **Notification**: Within 1 week
|
||||
- **Acknowledgment**: Within 1 week
|
||||
- **Plan**: Within sprint planning
|
||||
- **Fix**: Within quarter (3 months)
|
||||
- **Verification**: Quarterly review
|
||||
- **Documentation**: Included in quarterly planning
|
||||
|
||||
### Low (P3)
|
||||
- **Notification**: Added to backlog
|
||||
- **Acknowledgment**: During backlog refinement
|
||||
- **Plan**: When capacity available
|
||||
- **Fix**: Opportunistic
|
||||
- **Verification**: As completed
|
||||
- **Documentation**: Optional
|
||||
|
||||
---
|
||||
|
||||
## Category-Specific Severity Guidelines
|
||||
|
||||
### Structure Issues
|
||||
- **Critical**: No features/, flat structure with 100+ components
|
||||
- **High**: Missing features/, cross-feature dependencies
|
||||
- **Medium**: Some organizational issues
|
||||
- **Low**: Minor folder organization improvements
|
||||
|
||||
### Component Issues
|
||||
- **Critical**: Components > 1000 LOC, widespread violations
|
||||
- **High**: Many components > 400 LOC, 15+ props
|
||||
- **Medium**: Some large components, nested renders
|
||||
- **Low**: Minor design improvements needed
|
||||
|
||||
### State Management
|
||||
- **Critical**: No proper state management in complex app
|
||||
- **High**: No data fetching library, manual API state
|
||||
- **Medium**: State could be better localized
|
||||
- **Low**: Could use better state management tool
|
||||
|
||||
### Testing Issues
|
||||
- **Critical**: No testing framework, 0% coverage
|
||||
- **High**: Coverage < 50%, wrong test distribution
|
||||
- **Medium**: Coverage 50-79%, some brittle tests
|
||||
- **Low**: Coverage > 80%, minor test improvements
|
||||
|
||||
### Security Issues
|
||||
- **Critical**: Tokens in localStorage, XSS vulnerabilities
|
||||
- **High**: No error tracking, missing CSRF protection
|
||||
- **Medium**: Minor security improvements needed
|
||||
- **Low**: Security best practices could be better
|
||||
|
||||
---
|
||||
|
||||
## Migration Planning
|
||||
|
||||
### Phase 1: Critical (Week 1)
|
||||
1. Fix all P0 security issues
|
||||
2. Establish basic architecture (features/)
|
||||
3. Set up testing framework
|
||||
4. Configure error tracking
|
||||
|
||||
### Phase 2: High Priority (Weeks 2-6)
|
||||
1. Migrate to feature-based structure
|
||||
2. Add React Query/SWR
|
||||
3. Improve test coverage to 80%
|
||||
4. Refactor large components
|
||||
5. Add error boundaries
|
||||
|
||||
### Phase 3: Medium Priority (Months 2-3)
|
||||
1. Optimize component architecture
|
||||
2. Implement code splitting
|
||||
3. Improve state management
|
||||
4. Add comprehensive testing
|
||||
5. Performance optimizations
|
||||
|
||||
### Phase 4: Low Priority (Ongoing)
|
||||
1. Stylistic improvements
|
||||
2. Developer experience enhancements
|
||||
3. Documentation updates
|
||||
4. Minor refactoring
|
||||
|
||||
---
|
||||
|
||||
**Note**: These guidelines should be adapted based on your team size, release cadence, and business priorities. Always balance technical debt reduction with feature development.
|
||||
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Bulletproof React Analyzers
|
||||
|
||||
Specialized analyzers for different aspects of Bulletproof React compliance.
|
||||
"""
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
API Layer Analyzer
|
||||
|
||||
Analyzes API organization against Bulletproof React patterns:
|
||||
- Centralized API client
|
||||
- Type-safe request declarations
|
||||
- Colocated in features/
|
||||
- Data fetching hooks
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import re
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze API layer architecture."""
|
||||
findings = []
|
||||
src_dir = codebase_path / 'src'
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for centralized API client
|
||||
has_api_config = (src_dir / 'lib').exists() or any(src_dir.rglob('**/api-client.*'))
|
||||
if not has_api_config:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'api',
|
||||
'title': 'No centralized API client detected',
|
||||
'current_state': 'No api-client configuration found in src/lib/',
|
||||
'target_state': 'Create single configured API client instance',
|
||||
'migration_steps': [
|
||||
'Create src/lib/api-client.ts with axios or fetch wrapper',
|
||||
'Configure base URL, headers, interceptors',
|
||||
'Export configured client',
|
||||
'Use in all API calls'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
# Check for scattered fetch calls
|
||||
scattered_fetches = []
|
||||
for file in src_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
if 'test' in str(file) or 'spec' in str(file):
|
||||
continue
|
||||
try:
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
if re.search(r'\bfetch\s*\(', content) and 'api' not in str(file).lower():
|
||||
scattered_fetches.append(str(file.relative_to(src_dir)))
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(scattered_fetches) > 3:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'api',
|
||||
'title': f'Scattered fetch calls in {len(scattered_fetches)} files',
|
||||
'current_state': 'fetch() calls throughout components',
|
||||
'target_state': 'Centralize API calls in feature api/ directories',
|
||||
'migration_steps': [
|
||||
'Create api/ directory in each feature',
|
||||
'Move API calls to dedicated functions',
|
||||
'Create custom hooks wrapping API calls',
|
||||
'Use React Query or SWR for data fetching'
|
||||
],
|
||||
'effort': 'high',
|
||||
'affected_files': scattered_fetches[:5],
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,323 @@
|
||||
"""
|
||||
Component Architecture Analyzer
|
||||
|
||||
Analyzes React component design against Bulletproof React principles:
|
||||
- Component colocation (near where they're used)
|
||||
- Limited props (< 7-10)
|
||||
- Reasonable component size (< 300 LOC)
|
||||
- No nested render functions
|
||||
- Proper composition over excessive props
|
||||
- Consistent naming (kebab-case files)
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""
|
||||
Analyze component architecture for Bulletproof React compliance.
|
||||
|
||||
Args:
|
||||
codebase_path: Path to React codebase
|
||||
metadata: Project metadata from discovery phase
|
||||
|
||||
Returns:
|
||||
List of findings with severity and migration guidance
|
||||
"""
|
||||
findings = []
|
||||
|
||||
src_dir = codebase_path / 'src'
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Analyze all React component files
|
||||
findings.extend(check_component_sizes(src_dir))
|
||||
findings.extend(check_component_props(src_dir))
|
||||
findings.extend(check_nested_render_functions(src_dir))
|
||||
findings.extend(check_file_naming_conventions(src_dir))
|
||||
findings.extend(check_component_colocation(src_dir))
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_component_sizes(src_dir: Path) -> List[Dict]:
|
||||
"""Check for overly large components."""
|
||||
findings = []
|
||||
exclude_dirs = {'node_modules', 'dist', 'build', '.next', 'coverage'}
|
||||
|
||||
large_components = []
|
||||
for component_file in src_dir.rglob('*.{tsx,jsx}'):
|
||||
if any(excluded in component_file.parts for excluded in exclude_dirs):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(component_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
lines = f.readlines()
|
||||
loc = len([line for line in lines if line.strip() and not line.strip().startswith('//')])
|
||||
|
||||
if loc > 300:
|
||||
large_components.append({
|
||||
'file': str(component_file.relative_to(src_dir)),
|
||||
'lines': loc,
|
||||
'severity': 'critical' if loc > 500 else 'high' if loc > 400 else 'medium'
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if large_components:
|
||||
# Report the worst offenders
|
||||
large_components.sort(key=lambda x: x['lines'], reverse=True)
|
||||
|
||||
for comp in large_components[:10]: # Top 10 largest
|
||||
findings.append({
|
||||
'severity': comp['severity'],
|
||||
'category': 'components',
|
||||
'title': f'Large component ({comp["lines"]} LOC)',
|
||||
'current_state': f'{comp["file"]} has {comp["lines"]} lines',
|
||||
'target_state': 'Components should be < 300 lines. Large components are hard to understand and test.',
|
||||
'migration_steps': [
|
||||
'Identify distinct responsibilities in the component',
|
||||
'Extract smaller components for each UI section',
|
||||
'Move business logic to custom hooks',
|
||||
'Extract complex rendering logic to separate components',
|
||||
'Consider splitting into multiple feature components'
|
||||
],
|
||||
'effort': 'high' if comp['lines'] > 400 else 'medium',
|
||||
'file': comp['file'],
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_component_props(src_dir: Path) -> List[Dict]:
|
||||
"""Check for components with excessive props."""
|
||||
findings = []
|
||||
exclude_dirs = {'node_modules', 'dist', 'build', '.next', 'coverage'}
|
||||
|
||||
components_with_many_props = []
|
||||
for component_file in src_dir.rglob('*.{tsx,jsx}'):
|
||||
if any(excluded in component_file.parts for excluded in exclude_dirs):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(component_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Find component definitions with props
|
||||
# Pattern matches: function Component({ prop1, prop2, ... })
|
||||
# and: const Component = ({ prop1, prop2, ... }) =>
|
||||
props_pattern = re.compile(
|
||||
r'(?:function|const)\s+(\w+)\s*(?:=\s*)?\(\s*\{([^}]+)\}',
|
||||
re.MULTILINE
|
||||
)
|
||||
|
||||
matches = props_pattern.findall(content)
|
||||
for component_name, props_str in matches:
|
||||
# Count props (split by comma)
|
||||
props = [p.strip() for p in props_str.split(',') if p.strip()]
|
||||
# Filter out destructured nested props
|
||||
actual_props = [p for p in props if not p.startswith('...')]
|
||||
prop_count = len(actual_props)
|
||||
|
||||
if prop_count > 10:
|
||||
components_with_many_props.append({
|
||||
'file': str(component_file.relative_to(src_dir)),
|
||||
'component': component_name,
|
||||
'prop_count': prop_count,
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if components_with_many_props:
|
||||
for comp in components_with_many_props:
|
||||
findings.append({
|
||||
'severity': 'critical' if comp['prop_count'] > 15 else 'high',
|
||||
'category': 'components',
|
||||
'title': f'Component with {comp["prop_count"]} props: {comp["component"]}',
|
||||
'current_state': f'{comp["file"]} has {comp["prop_count"]} props',
|
||||
'target_state': 'Components should accept < 7-10 props. Too many props indicates insufficient composition.',
|
||||
'migration_steps': [
|
||||
'Group related props into configuration objects',
|
||||
'Use composition (children prop) instead of render props',
|
||||
'Extract sub-components with their own props',
|
||||
'Consider using Context for deeply shared state',
|
||||
'Use compound component pattern for complex UIs'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'file': comp['file'],
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_nested_render_functions(src_dir: Path) -> List[Dict]:
|
||||
"""Check for nested render functions inside components."""
|
||||
findings = []
|
||||
exclude_dirs = {'node_modules', 'dist', 'build', '.next', 'coverage'}
|
||||
|
||||
nested_render_functions = []
|
||||
for component_file in src_dir.rglob('*.{tsx,jsx}'):
|
||||
if any(excluded in component_file.parts for excluded in exclude_dirs):
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(component_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
lines = content.split('\n')
|
||||
|
||||
# Look for patterns like: const renderSomething = () => { ... }
|
||||
# or: function renderSomething() { ... }
|
||||
nested_render_pattern = re.compile(r'(?:const|function)\s+(render\w+)\s*[=:]?\s*\([^)]*\)\s*(?:=>)?\s*\{')
|
||||
|
||||
for line_num, line in enumerate(lines, start=1):
|
||||
if nested_render_pattern.search(line):
|
||||
nested_render_functions.append({
|
||||
'file': str(component_file.relative_to(src_dir)),
|
||||
'line': line_num,
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if nested_render_functions:
|
||||
# Group by file
|
||||
files_with_nested = {}
|
||||
for item in nested_render_functions:
|
||||
file = item['file']
|
||||
if file not in files_with_nested:
|
||||
files_with_nested[file] = []
|
||||
files_with_nested[file].append(item['line'])
|
||||
|
||||
for file, lines in files_with_nested.items():
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'components',
|
||||
'title': f'Nested render functions detected ({len(lines)} instances)',
|
||||
'current_state': f'{file} contains render functions inside component',
|
||||
'target_state': 'Extract nested render functions into separate components for better reusability and testing.',
|
||||
'migration_steps': [
|
||||
'Identify each render function and its dependencies',
|
||||
'Extract to separate component file',
|
||||
'Pass necessary props to new component',
|
||||
'Update tests to test new component in isolation',
|
||||
'Remove render function from parent component'
|
||||
],
|
||||
'effort': 'low',
|
||||
'file': file,
|
||||
'affected_lines': lines[:5], # Show first 5
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_file_naming_conventions(src_dir: Path) -> List[Dict]:
|
||||
"""Check for consistent kebab-case file naming."""
|
||||
findings = []
|
||||
exclude_dirs = {'node_modules', 'dist', 'build', '.next', 'coverage'}
|
||||
|
||||
non_kebab_files = []
|
||||
for file_path in src_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
if any(excluded in file_path.parts for excluded in exclude_dirs):
|
||||
continue
|
||||
|
||||
filename = file_path.stem # filename without extension
|
||||
|
||||
# Check if filename is kebab-case (lowercase with hyphens)
|
||||
# Allow: kebab-case.tsx, lowercase.tsx
|
||||
# Disallow: PascalCase.tsx, camelCase.tsx, snake_case.tsx
|
||||
is_kebab_or_lowercase = re.match(r'^[a-z][a-z0-9]*(-[a-z0-9]+)*$', filename)
|
||||
|
||||
if not is_kebab_or_lowercase and filename not in ['index', 'App']: # Allow common exceptions
|
||||
non_kebab_files.append(str(file_path.relative_to(src_dir)))
|
||||
|
||||
if len(non_kebab_files) > 5: # Only report if it's a pattern (>5 files)
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'components',
|
||||
'title': f'Inconsistent file naming ({len(non_kebab_files)} files)',
|
||||
'current_state': f'{len(non_kebab_files)} files not using kebab-case naming',
|
||||
'target_state': 'Bulletproof React recommends kebab-case for all files (e.g., user-profile.tsx)',
|
||||
'migration_steps': [
|
||||
'Rename files to kebab-case format',
|
||||
'Update all import statements',
|
||||
'Run tests to ensure nothing broke',
|
||||
'Add ESLint rule to enforce kebab-case (unicorn/filename-case)'
|
||||
],
|
||||
'effort': 'low',
|
||||
'affected_files': non_kebab_files[:10], # Show first 10
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_component_colocation(src_dir: Path) -> List[Dict]:
|
||||
"""Check if components are colocated near where they're used."""
|
||||
findings = []
|
||||
|
||||
components_dir = src_dir / 'components'
|
||||
if not components_dir.exists():
|
||||
return findings
|
||||
|
||||
# Find components in shared components/ that are only used once
|
||||
single_use_components = []
|
||||
for component_file in components_dir.rglob('*.{tsx,jsx}'):
|
||||
try:
|
||||
component_name = component_file.stem
|
||||
|
||||
# Search for imports of this component across codebase
|
||||
import_pattern = re.compile(rf'import.*{component_name}.*from.*[\'"]/|@/')
|
||||
usage_count = 0
|
||||
used_in_feature = None
|
||||
|
||||
for search_file in src_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
if search_file == component_file:
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(search_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
if import_pattern.search(content):
|
||||
usage_count += 1
|
||||
|
||||
# Check if used in a feature
|
||||
if 'features' in search_file.parts:
|
||||
features_index = search_file.parts.index('features')
|
||||
if features_index + 1 < len(search_file.parts):
|
||||
feature_name = search_file.parts[features_index + 1]
|
||||
if used_in_feature is None:
|
||||
used_in_feature = feature_name
|
||||
elif used_in_feature != feature_name:
|
||||
used_in_feature = 'multiple'
|
||||
except:
|
||||
pass
|
||||
|
||||
# If used only in one feature, it should be colocated there
|
||||
if usage_count == 1 and used_in_feature and used_in_feature != 'multiple':
|
||||
single_use_components.append({
|
||||
'file': str(component_file.relative_to(src_dir)),
|
||||
'component': component_name,
|
||||
'feature': used_in_feature,
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if single_use_components:
|
||||
for comp in single_use_components[:5]: # Top 5
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'components',
|
||||
'title': f'Component used in only one feature: {comp["component"]}',
|
||||
'current_state': f'{comp["file"]} is in shared components/ but only used in {comp["feature"]} feature',
|
||||
'target_state': 'Components used by only one feature should be colocated in that feature directory.',
|
||||
'migration_steps': [
|
||||
f'Move {comp["file"]} to src/features/{comp["feature"]}/components/',
|
||||
'Update import in the feature',
|
||||
'Run tests to verify',
|
||||
'Remove from shared components/'
|
||||
],
|
||||
'effort': 'low',
|
||||
'file': comp['file'],
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
Error Handling Analyzer
|
||||
|
||||
Analyzes error handling patterns:
|
||||
- Error boundaries present
|
||||
- API error interceptors
|
||||
- Error tracking (Sentry)
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import re
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze error handling patterns."""
|
||||
findings = []
|
||||
src_dir = codebase_path / 'src'
|
||||
tech_stack = metadata.get('tech_stack', {})
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for error boundaries
|
||||
error_boundaries = list(src_dir.rglob('**/error-boundary.*')) + \
|
||||
list(src_dir.rglob('**/ErrorBoundary.*'))
|
||||
|
||||
if not error_boundaries:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'errors',
|
||||
'title': 'No error boundaries detected',
|
||||
'current_state': 'No ErrorBoundary components found',
|
||||
'target_state': 'Implement multiple error boundaries at strategic locations',
|
||||
'migration_steps': [
|
||||
'Create ErrorBoundary component with componentDidCatch',
|
||||
'Wrap route components with ErrorBoundary',
|
||||
'Add feature-level error boundaries',
|
||||
'Display user-friendly error messages'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
# Check for error tracking
|
||||
if not tech_stack.get('sentry'):
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'errors',
|
||||
'title': 'No error tracking service detected',
|
||||
'current_state': 'No Sentry or similar error tracking',
|
||||
'target_state': 'Use Sentry for production error monitoring',
|
||||
'migration_steps': [
|
||||
'Sign up for Sentry',
|
||||
'Install @sentry/react',
|
||||
'Configure Sentry.init() in app entry',
|
||||
'Add user context and tags',
|
||||
'Set up error alerts'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,76 @@
|
||||
"""
|
||||
Performance Patterns Analyzer
|
||||
|
||||
Analyzes React performance optimizations:
|
||||
- Code splitting at routes
|
||||
- Memoization patterns
|
||||
- Image optimization
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import re
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze performance patterns."""
|
||||
findings = []
|
||||
src_dir = codebase_path / 'src'
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for lazy loading
|
||||
has_lazy_loading = False
|
||||
for file in src_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
try:
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
if 'React.lazy' in content or 'lazy(' in content:
|
||||
has_lazy_loading = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
|
||||
if not has_lazy_loading:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'performance',
|
||||
'title': 'No code splitting detected',
|
||||
'current_state': 'No React.lazy() usage found',
|
||||
'target_state': 'Use code splitting for routes and large components',
|
||||
'migration_steps': [
|
||||
'Wrap route components with React.lazy()',
|
||||
'Add Suspense boundaries with loading states',
|
||||
'Split large features into separate chunks',
|
||||
'Analyze bundle size with build tools'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
# Check for large images
|
||||
assets_dir = codebase_path / 'public' / 'assets'
|
||||
if assets_dir.exists():
|
||||
large_images = []
|
||||
for img in assets_dir.rglob('*.{jpg,jpeg,png,gif}'):
|
||||
size_mb = img.stat().st_size / (1024 * 1024)
|
||||
if size_mb > 0.5: # Larger than 500KB
|
||||
large_images.append((str(img.name), size_mb))
|
||||
|
||||
if large_images:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'performance',
|
||||
'title': f'{len(large_images)} large images detected',
|
||||
'current_state': f'Images larger than 500KB',
|
||||
'target_state': 'Optimize images with modern formats and lazy loading',
|
||||
'migration_steps': [
|
||||
'Convert to WebP format',
|
||||
'Add lazy loading with loading="lazy"',
|
||||
'Use srcset for responsive images',
|
||||
'Compress images with tools like sharp'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,369 @@
|
||||
"""
|
||||
Project Structure Analyzer
|
||||
|
||||
Analyzes React project structure against Bulletproof React patterns:
|
||||
- Feature-based organization (src/features/)
|
||||
- Unidirectional dependencies (shared → features → app)
|
||||
- No cross-feature imports
|
||||
- Proper folder hierarchy
|
||||
"""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Set
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""
|
||||
Analyze project structure for Bulletproof React compliance.
|
||||
|
||||
Args:
|
||||
codebase_path: Path to React codebase
|
||||
metadata: Project metadata from discovery phase
|
||||
|
||||
Returns:
|
||||
List of findings with severity and migration guidance
|
||||
"""
|
||||
findings = []
|
||||
|
||||
src_dir = codebase_path / 'src'
|
||||
if not src_dir.exists():
|
||||
findings.append({
|
||||
'severity': 'critical',
|
||||
'category': 'structure',
|
||||
'title': 'Missing src/ directory',
|
||||
'current_state': 'No src/ directory found',
|
||||
'target_state': 'All source code should be in src/ directory',
|
||||
'migration_steps': [
|
||||
'Create src/ directory',
|
||||
'Move all source files to src/',
|
||||
'Update import paths',
|
||||
'Update build configuration'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
return findings
|
||||
|
||||
# Check for Bulletproof structure
|
||||
findings.extend(check_bulletproof_structure(src_dir))
|
||||
|
||||
# Check for cross-feature imports
|
||||
findings.extend(check_cross_feature_imports(src_dir))
|
||||
|
||||
# Analyze features/ organization
|
||||
findings.extend(analyze_features_directory(src_dir))
|
||||
|
||||
# Check shared code organization
|
||||
findings.extend(check_shared_code_organization(src_dir))
|
||||
|
||||
# Check for architectural violations
|
||||
findings.extend(check_architectural_violations(src_dir))
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_bulletproof_structure(src_dir: Path) -> List[Dict]:
|
||||
"""Check for presence of Bulletproof React folder structure."""
|
||||
findings = []
|
||||
|
||||
# Required top-level directories for Bulletproof React
|
||||
bulletproof_dirs = {
|
||||
'app': 'Application layer (routes, app.tsx, provider.tsx, router.tsx)',
|
||||
'features': 'Feature modules (80%+ of code should be here)',
|
||||
}
|
||||
|
||||
# Recommended directories
|
||||
recommended_dirs = {
|
||||
'components': 'Shared components used across multiple features',
|
||||
'hooks': 'Shared custom hooks',
|
||||
'lib': 'Third-party library configurations',
|
||||
'utils': 'Shared utility functions',
|
||||
'types': 'Shared TypeScript types',
|
||||
}
|
||||
|
||||
# Check required directories
|
||||
for dir_name, description in bulletproof_dirs.items():
|
||||
dir_path = src_dir / dir_name
|
||||
if not dir_path.exists():
|
||||
findings.append({
|
||||
'severity': 'critical' if dir_name == 'features' else 'high',
|
||||
'category': 'structure',
|
||||
'title': f'Missing {dir_name}/ directory',
|
||||
'current_state': f'No {dir_name}/ directory found',
|
||||
'target_state': f'{dir_name}/ directory should exist: {description}',
|
||||
'migration_steps': [
|
||||
f'Create src/{dir_name}/ directory',
|
||||
f'Organize code according to Bulletproof React {dir_name} pattern',
|
||||
'Update imports to use new structure'
|
||||
],
|
||||
'effort': 'high' if dir_name == 'features' else 'medium',
|
||||
})
|
||||
|
||||
# Check recommended directories (lower severity)
|
||||
missing_recommended = []
|
||||
for dir_name, description in recommended_dirs.items():
|
||||
dir_path = src_dir / dir_name
|
||||
if not dir_path.exists():
|
||||
missing_recommended.append(f'{dir_name}/ ({description})')
|
||||
|
||||
if missing_recommended:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'structure',
|
||||
'title': 'Missing recommended directories',
|
||||
'current_state': f'Missing: {", ".join([d.split("/")[0] for d in missing_recommended])}',
|
||||
'target_state': 'Bulletproof React recommends these directories for shared code',
|
||||
'migration_steps': [
|
||||
'Create missing directories as needed',
|
||||
'Move shared code to appropriate directories',
|
||||
'Ensure proper separation between shared and feature-specific code'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_cross_feature_imports(src_dir: Path) -> List[Dict]:
|
||||
"""Detect cross-feature imports (architectural violation)."""
|
||||
findings = []
|
||||
features_dir = src_dir / 'features'
|
||||
|
||||
if not features_dir.exists():
|
||||
return findings
|
||||
|
||||
# Get all feature directories
|
||||
feature_dirs = [d for d in features_dir.iterdir() if d.is_dir() and not d.name.startswith('.')]
|
||||
|
||||
violations = []
|
||||
for feature_dir in feature_dirs:
|
||||
# Find all TypeScript/JavaScript files in this feature
|
||||
for file_path in feature_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check for imports from other features
|
||||
import_pattern = re.compile(r'from\s+[\'"]([^\'\"]+)[\'"]')
|
||||
imports = import_pattern.findall(content)
|
||||
|
||||
for imp in imports:
|
||||
# Check if importing from another feature
|
||||
if imp.startswith('../') or imp.startswith('@/features/'):
|
||||
# Extract feature name from import path
|
||||
if '@/features/' in imp:
|
||||
imported_feature = imp.split('@/features/')[1].split('/')[0]
|
||||
elif '../' in imp:
|
||||
# Handle relative imports
|
||||
parts = imp.split('/')
|
||||
if 'features' in parts:
|
||||
idx = parts.index('features')
|
||||
if idx + 1 < len(parts):
|
||||
imported_feature = parts[idx + 1]
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
|
||||
# Check if importing from different feature
|
||||
current_feature = feature_dir.name
|
||||
if imported_feature != current_feature and imported_feature in [f.name for f in feature_dirs]:
|
||||
violations.append({
|
||||
'file': str(file_path.relative_to(src_dir)),
|
||||
'from_feature': current_feature,
|
||||
'to_feature': imported_feature,
|
||||
'import': imp
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if violations:
|
||||
# Group violations by feature
|
||||
grouped = {}
|
||||
for v in violations:
|
||||
key = f"{v['from_feature']} → {v['to_feature']}"
|
||||
if key not in grouped:
|
||||
grouped[key] = []
|
||||
grouped[key].append(v['file'])
|
||||
|
||||
for import_path, files in grouped.items():
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'structure',
|
||||
'title': f'Cross-feature import: {import_path}',
|
||||
'current_state': f'{len(files)} file(s) import from another feature',
|
||||
'target_state': 'Features should be independent. Shared code belongs in src/components/, src/hooks/, or src/utils/',
|
||||
'migration_steps': [
|
||||
'Identify what code is being shared between features',
|
||||
'Move truly shared code to src/components/, src/hooks/, or src/utils/',
|
||||
'If code is feature-specific, duplicate it or refactor feature boundaries',
|
||||
'Update imports to use shared code location'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'affected_files': files[:5], # Show first 5 files
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def analyze_features_directory(src_dir: Path) -> List[Dict]:
|
||||
"""Analyze features/ directory structure."""
|
||||
findings = []
|
||||
features_dir = src_dir / 'features'
|
||||
|
||||
if not features_dir.exists():
|
||||
return findings
|
||||
|
||||
feature_dirs = [d for d in features_dir.iterdir() if d.is_dir() and not d.name.startswith('.')]
|
||||
|
||||
if len(feature_dirs) == 0:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'structure',
|
||||
'title': 'Empty features/ directory',
|
||||
'current_state': 'features/ directory exists but contains no features',
|
||||
'target_state': '80%+ of application code should be organized in feature modules',
|
||||
'migration_steps': [
|
||||
'Identify distinct features in your application',
|
||||
'Create a directory for each feature in src/features/',
|
||||
'Move feature-specific code to appropriate feature directories',
|
||||
'Organize each feature with api/, components/, hooks/, stores/, types/, utils/ as needed'
|
||||
],
|
||||
'effort': 'high',
|
||||
})
|
||||
return findings
|
||||
|
||||
# Check each feature for proper internal structure
|
||||
for feature_dir in feature_dirs:
|
||||
feature_name = feature_dir.name
|
||||
|
||||
# Recommended feature subdirectories
|
||||
feature_subdirs = ['api', 'components', 'hooks', 'stores', 'types', 'utils']
|
||||
has_subdirs = any((feature_dir / subdir).exists() for subdir in feature_subdirs)
|
||||
|
||||
# Count files in feature root
|
||||
root_files = [f for f in feature_dir.iterdir() if f.is_file() and f.suffix in {'.ts', '.tsx', '.js', '.jsx'}]
|
||||
|
||||
if len(root_files) > 5 and not has_subdirs:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'structure',
|
||||
'title': f'Feature "{feature_name}" lacks internal organization',
|
||||
'current_state': f'{len(root_files)} files in feature root without subdirectories',
|
||||
'target_state': 'Features should be organized with api/, components/, hooks/, stores/, types/, utils/ subdirectories',
|
||||
'migration_steps': [
|
||||
f'Create subdirectories in src/features/{feature_name}/',
|
||||
'Move API calls to api/',
|
||||
'Move components to components/',
|
||||
'Move hooks to hooks/',
|
||||
'Move types to types/',
|
||||
'Move utilities to utils/'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_shared_code_organization(src_dir: Path) -> List[Dict]:
|
||||
"""Check if shared code is properly organized."""
|
||||
findings = []
|
||||
|
||||
components_dir = src_dir / 'components'
|
||||
features_dir = src_dir / 'features'
|
||||
|
||||
if not components_dir.exists():
|
||||
return findings
|
||||
|
||||
# Count components
|
||||
shared_components = list(components_dir.rglob('*.{tsx,jsx}'))
|
||||
shared_count = len(shared_components)
|
||||
|
||||
# Count feature components
|
||||
feature_count = 0
|
||||
if features_dir.exists():
|
||||
feature_count = len(list(features_dir.rglob('**/components/**/*.{tsx,jsx}')))
|
||||
|
||||
total_components = shared_count + feature_count
|
||||
|
||||
if total_components > 0:
|
||||
shared_percentage = (shared_count / total_components) * 100
|
||||
|
||||
# Bulletproof React recommends 80%+ code in features
|
||||
if shared_percentage > 40:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'structure',
|
||||
'title': 'Too many shared components',
|
||||
'current_state': f'{shared_percentage:.1f}% of components are in src/components/ (shared)',
|
||||
'target_state': 'Most components should be feature-specific. Only truly shared components belong in src/components/',
|
||||
'migration_steps': [
|
||||
'Review each component in src/components/',
|
||||
'Identify components used by only one feature',
|
||||
'Move feature-specific components to their feature directories',
|
||||
'Keep only truly shared components in src/components/'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_architectural_violations(src_dir: Path) -> List[Dict]:
|
||||
"""Check for common architectural violations."""
|
||||
findings = []
|
||||
|
||||
# Check for business logic in components/
|
||||
components_dir = src_dir / 'components'
|
||||
if components_dir.exists():
|
||||
large_components = []
|
||||
for component_file in components_dir.rglob('*.{tsx,jsx}'):
|
||||
try:
|
||||
with open(component_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
lines = len(f.readlines())
|
||||
if lines > 200:
|
||||
large_components.append((str(component_file.relative_to(src_dir)), lines))
|
||||
except:
|
||||
pass
|
||||
|
||||
if large_components:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'structure',
|
||||
'title': 'Large components in shared components/',
|
||||
'current_state': f'{len(large_components)} component(s) > 200 lines in src/components/',
|
||||
'target_state': 'Shared components should be simple and reusable. Complex components likely belong in features/',
|
||||
'migration_steps': [
|
||||
'Review large shared components',
|
||||
'Extract business logic to feature-specific hooks or utilities',
|
||||
'Consider moving complex components to features/ if feature-specific',
|
||||
'Keep shared components simple and focused'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'affected_files': [f[0] for f in large_components[:5]],
|
||||
})
|
||||
|
||||
# Check for proper app/ structure
|
||||
app_dir = src_dir / 'app'
|
||||
if app_dir.exists():
|
||||
expected_app_files = ['app.tsx', 'provider.tsx', 'router.tsx']
|
||||
has_routing = any((app_dir / f).exists() or (app_dir / 'routes').exists() for f in ['router.tsx', 'routes.tsx'])
|
||||
|
||||
if not has_routing:
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'structure',
|
||||
'title': 'Missing routing configuration in app/',
|
||||
'current_state': 'No router.tsx or routes/ found in src/app/',
|
||||
'target_state': 'Bulletproof React recommends centralizing routing in src/app/router.tsx or src/app/routes/',
|
||||
'migration_steps': [
|
||||
'Create src/app/router.tsx or src/app/routes/',
|
||||
'Define all application routes in one place',
|
||||
'Use code splitting for route-level lazy loading'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
Security Practices Analyzer
|
||||
|
||||
Analyzes React security patterns:
|
||||
- JWT with HttpOnly cookies
|
||||
- Input sanitization
|
||||
- XSS prevention
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import re
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze security practices."""
|
||||
findings = []
|
||||
src_dir = codebase_path / 'src'
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for localStorage token storage (security risk)
|
||||
localstorage_auth = []
|
||||
for file in src_dir.rglob('*.{ts,tsx,js,jsx}'):
|
||||
try:
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
if re.search(r'localStorage\.(get|set)Item\s*\(\s*[\'"].*token.*[\'"]\s*\)', content, re.IGNORECASE):
|
||||
localstorage_auth.append(str(file.relative_to(src_dir)))
|
||||
except:
|
||||
pass
|
||||
|
||||
if localstorage_auth:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'security',
|
||||
'title': f'Tokens stored in localStorage ({len(localstorage_auth)} files)',
|
||||
'current_state': 'Authentication tokens in localStorage (XSS vulnerable)',
|
||||
'target_state': 'Use HttpOnly cookies for JWT storage',
|
||||
'migration_steps': [
|
||||
'Configure API to set tokens in HttpOnly cookies',
|
||||
'Remove localStorage token storage',
|
||||
'Use credentials: "include" in fetch requests',
|
||||
'Implement CSRF protection'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'affected_files': localstorage_auth[:3],
|
||||
})
|
||||
|
||||
# Check for dangerouslySetInnerHTML
|
||||
dangerous_html = []
|
||||
for file in src_dir.rglob('*.{tsx,jsx}'):
|
||||
try:
|
||||
with open(file, 'r') as f:
|
||||
content = f.read()
|
||||
if 'dangerouslySetInnerHTML' in content:
|
||||
dangerous_html.append(str(file.relative_to(src_dir)))
|
||||
except:
|
||||
pass
|
||||
|
||||
if dangerous_html:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'security',
|
||||
'title': f'dangerouslySetInnerHTML usage ({len(dangerous_html)} files)',
|
||||
'current_state': 'Using dangerouslySetInnerHTML (XSS risk)',
|
||||
'target_state': 'Sanitize HTML input with DOMPurify',
|
||||
'migration_steps': [
|
||||
'Install dompurify',
|
||||
'Sanitize HTML before rendering',
|
||||
'Prefer safe alternatives when possible',
|
||||
'Add security review for HTML rendering'
|
||||
],
|
||||
'effort': 'low',
|
||||
'affected_files': dangerous_html[:3],
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,105 @@
|
||||
"""
|
||||
Standards Compliance Analyzer
|
||||
|
||||
Analyzes project standards:
|
||||
- ESLint configuration
|
||||
- TypeScript strict mode
|
||||
- Prettier setup
|
||||
- Git hooks (Husky)
|
||||
- Naming conventions
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
import json
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze standards compliance."""
|
||||
findings = []
|
||||
tech_stack = metadata.get('tech_stack', {})
|
||||
|
||||
# Check ESLint
|
||||
eslint_config = any([
|
||||
(codebase_path / '.eslintrc.js').exists(),
|
||||
(codebase_path / '.eslintrc.json').exists(),
|
||||
(codebase_path / 'eslint.config.js').exists(),
|
||||
])
|
||||
|
||||
if not eslint_config:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'standards',
|
||||
'title': 'No ESLint configuration',
|
||||
'current_state': 'No .eslintrc or eslint.config found',
|
||||
'target_state': 'Configure ESLint with React and TypeScript rules',
|
||||
'migration_steps': [
|
||||
'Install eslint and plugins',
|
||||
'Create .eslintrc.js configuration',
|
||||
'Add recommended rules for React and TS',
|
||||
'Add lint script to package.json',
|
||||
'Fix existing violations'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
# Check TypeScript strict mode
|
||||
tsconfig = codebase_path / 'tsconfig.json'
|
||||
if tsconfig.exists():
|
||||
try:
|
||||
with open(tsconfig, 'r') as f:
|
||||
config = json.load(f)
|
||||
strict = config.get('compilerOptions', {}).get('strict', False)
|
||||
if not strict:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'standards',
|
||||
'title': 'TypeScript strict mode disabled',
|
||||
'current_state': 'strict: false in tsconfig.json',
|
||||
'target_state': 'Enable strict mode for better type safety',
|
||||
'migration_steps': [
|
||||
'Set "strict": true in compilerOptions',
|
||||
'Fix type errors incrementally',
|
||||
'Add explicit return types',
|
||||
'Remove any types'
|
||||
],
|
||||
'effort': 'high',
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check Prettier
|
||||
if not tech_stack.get('prettier'):
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'standards',
|
||||
'title': 'No Prettier detected',
|
||||
'current_state': 'Prettier not in dependencies',
|
||||
'target_state': 'Use Prettier for consistent code formatting',
|
||||
'migration_steps': [
|
||||
'Install prettier',
|
||||
'Create .prettierrc configuration',
|
||||
'Enable "format on save" in IDE',
|
||||
'Run prettier on all files'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
# Check Husky
|
||||
if not tech_stack.get('husky'):
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'standards',
|
||||
'title': 'No git hooks (Husky) detected',
|
||||
'current_state': 'No pre-commit hooks',
|
||||
'target_state': 'Use Husky for pre-commit linting and testing',
|
||||
'migration_steps': [
|
||||
'Install husky and lint-staged',
|
||||
'Set up pre-commit hook',
|
||||
'Run lint and type-check before commits',
|
||||
'Prevent bad code from entering repo'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
State Management Analyzer
|
||||
|
||||
Analyzes React state management against Bulletproof React principles:
|
||||
- Appropriate tool for each state type (component, app, server, form, URL)
|
||||
- State localized when possible
|
||||
- Server cache separated (React Query/SWR)
|
||||
- No global state overuse
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""
|
||||
Analyze state management patterns.
|
||||
|
||||
Args:
|
||||
codebase_path: Path to React codebase
|
||||
metadata: Project metadata from discovery phase
|
||||
|
||||
Returns:
|
||||
List of findings with severity and migration guidance
|
||||
"""
|
||||
findings = []
|
||||
|
||||
tech_stack = metadata.get('tech_stack', {})
|
||||
src_dir = codebase_path / 'src'
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for appropriate state management tools
|
||||
findings.extend(check_state_management_tools(tech_stack))
|
||||
|
||||
# Check for data fetching library (server cache state)
|
||||
findings.extend(check_data_fetching_library(tech_stack))
|
||||
|
||||
# Check for form state management
|
||||
findings.extend(check_form_state_management(src_dir, tech_stack))
|
||||
|
||||
# Check for potential state management issues
|
||||
findings.extend(check_state_patterns(src_dir))
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_state_management_tools(tech_stack: Dict) -> List[Dict]:
|
||||
"""Check for presence of appropriate state management tools."""
|
||||
findings = []
|
||||
|
||||
# Check if any global state management is present
|
||||
has_state_mgmt = any([
|
||||
tech_stack.get('redux'),
|
||||
tech_stack.get('zustand'),
|
||||
tech_stack.get('jotai'),
|
||||
tech_stack.get('mobx')
|
||||
])
|
||||
|
||||
# If app has many features but no state management, might need it
|
||||
# (This is a heuristic - could be Context-based which is fine)
|
||||
if not has_state_mgmt:
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'state',
|
||||
'title': 'No explicit global state management detected',
|
||||
'current_state': 'No Redux, Zustand, Jotai, or MobX found',
|
||||
'target_state': 'Consider Zustand or Jotai for global state if Context becomes complex. Start with Context + hooks.',
|
||||
'migration_steps': [
|
||||
'Evaluate if Context API is sufficient for your needs',
|
||||
'If Context becomes complex, consider Zustand (simple) or Jotai (atomic)',
|
||||
'Avoid Redux unless you need its ecosystem (Redux Toolkit simplifies it)',
|
||||
'Keep state as local as possible before going global'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_data_fetching_library(tech_stack: Dict) -> List[Dict]:
|
||||
"""Check for React Query, SWR, or similar for server state."""
|
||||
findings = []
|
||||
|
||||
has_data_fetching = any([
|
||||
tech_stack.get('react-query'),
|
||||
tech_stack.get('swr'),
|
||||
tech_stack.get('apollo'),
|
||||
tech_stack.get('rtk-query')
|
||||
])
|
||||
|
||||
if not has_data_fetching:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'state',
|
||||
'title': 'No data fetching library detected',
|
||||
'current_state': 'No React Query, SWR, Apollo Client, or RTK Query found',
|
||||
'target_state': 'Use React Query or SWR for server state management (caching, refetching, optimistic updates)',
|
||||
'migration_steps': [
|
||||
'Install React Query (@tanstack/react-query) or SWR',
|
||||
'Wrap app with QueryClientProvider (React Query) or SWRConfig (SWR)',
|
||||
'Convert fetch calls to useQuery hooks',
|
||||
'Replace manual loading/error states with library patterns',
|
||||
'Add staleTime, cacheTime configurations as needed'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_form_state_management(src_dir: Path, tech_stack: Dict) -> List[Dict]:
|
||||
"""Check for form state management."""
|
||||
findings = []
|
||||
|
||||
has_form_lib = any([
|
||||
tech_stack.get('react-hook-form'),
|
||||
tech_stack.get('formik')
|
||||
])
|
||||
|
||||
# Look for form components without form library
|
||||
if not has_form_lib:
|
||||
form_files = []
|
||||
for file_path in src_dir.rglob('*.{tsx,jsx}'):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
# Look for <form> tags
|
||||
if re.search(r'<form[>\s]', content, re.IGNORECASE):
|
||||
form_files.append(str(file_path.relative_to(src_dir)))
|
||||
except:
|
||||
pass
|
||||
|
||||
if len(form_files) > 3: # More than 3 forms suggests need for form library
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'state',
|
||||
'title': f'No form library but {len(form_files)} forms detected',
|
||||
'current_state': f'{len(form_files)} form components without React Hook Form or Formik',
|
||||
'target_state': 'Use React Hook Form for performant form state management',
|
||||
'migration_steps': [
|
||||
'Install react-hook-form',
|
||||
'Replace controlled form state with useForm() hook',
|
||||
'Use register() for input registration',
|
||||
'Handle validation with yup or zod schemas',
|
||||
'Reduce re-renders with uncontrolled inputs'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'affected_files': form_files[:5],
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_state_patterns(src_dir: Path) -> List[Dict]:
|
||||
"""Check for common state management anti-patterns."""
|
||||
findings = []
|
||||
|
||||
# Look for large Context providers (potential performance issue)
|
||||
large_contexts = []
|
||||
for file_path in src_dir.rglob('*.{tsx,jsx}'):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Look for Context creation with many values
|
||||
if 'createContext' in content:
|
||||
# Count useState hooks in the provider
|
||||
state_count = len(re.findall(r'useState\s*\(', content))
|
||||
if state_count > 5:
|
||||
large_contexts.append({
|
||||
'file': str(file_path.relative_to(src_dir)),
|
||||
'state_count': state_count
|
||||
})
|
||||
except:
|
||||
pass
|
||||
|
||||
if large_contexts:
|
||||
for ctx in large_contexts:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'state',
|
||||
'title': f'Large Context with {ctx["state_count"]} state values',
|
||||
'current_state': f'{ctx["file"]} has many state values in one Context',
|
||||
'target_state': 'Split large Contexts into smaller, focused Contexts to prevent unnecessary re-renders',
|
||||
'migration_steps': [
|
||||
'Identify which state values change together',
|
||||
'Create separate Contexts for independent state',
|
||||
'Use Context composition for related state',
|
||||
'Consider Zustand/Jotai for complex global state'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'file': ctx['file'],
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,59 @@
|
||||
"""
|
||||
Styling Patterns Analyzer
|
||||
|
||||
Analyzes styling approach against Bulletproof React:
|
||||
- Consistent styling method
|
||||
- Component library usage
|
||||
- Colocated styles
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""Analyze styling patterns."""
|
||||
findings = []
|
||||
tech_stack = metadata.get('tech_stack', {})
|
||||
|
||||
# Check for styling approach
|
||||
styling_tools = []
|
||||
if tech_stack.get('tailwind'): styling_tools.append('Tailwind CSS')
|
||||
if tech_stack.get('styled-components'): styling_tools.append('styled-components')
|
||||
if tech_stack.get('emotion'): styling_tools.append('Emotion')
|
||||
if tech_stack.get('chakra-ui'): styling_tools.append('Chakra UI')
|
||||
if tech_stack.get('mui'): styling_tools.append('Material UI')
|
||||
if tech_stack.get('radix-ui'): styling_tools.append('Radix UI')
|
||||
|
||||
if not styling_tools:
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'styling',
|
||||
'title': 'No component library or utility CSS detected',
|
||||
'current_state': 'No Tailwind, Chakra UI, Radix UI, or other styling system',
|
||||
'target_state': 'Use component library (Chakra, Radix) or utility CSS (Tailwind)',
|
||||
'migration_steps': [
|
||||
'Choose styling approach based on needs',
|
||||
'Install Tailwind CSS (utility-first) or Chakra UI (component library)',
|
||||
'Configure theme and design tokens',
|
||||
'Migrate components gradually'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
elif len(styling_tools) > 2:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'styling',
|
||||
'title': f'Multiple styling approaches ({len(styling_tools)})',
|
||||
'current_state': f'Using: {", ".join(styling_tools)}',
|
||||
'target_state': 'Standardize on single styling approach',
|
||||
'migration_steps': [
|
||||
'Choose primary styling system',
|
||||
'Create migration plan',
|
||||
'Update style guide',
|
||||
'Refactor components incrementally'
|
||||
],
|
||||
'effort': 'high',
|
||||
})
|
||||
|
||||
return findings
|
||||
@@ -0,0 +1,313 @@
|
||||
"""
|
||||
Testing Strategy Analyzer
|
||||
|
||||
Analyzes React testing against Bulletproof React and Connor's standards:
|
||||
- Testing trophy distribution (70% integration, 20% unit, 10% E2E)
|
||||
- 80%+ coverage requirement
|
||||
- Semantic queries (getByRole preferred)
|
||||
- User behavior testing (not implementation details)
|
||||
- Test naming ("should X when Y")
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
|
||||
def analyze(codebase_path: Path, metadata: Dict) -> List[Dict]:
|
||||
"""
|
||||
Analyze testing strategy and quality.
|
||||
|
||||
Args:
|
||||
codebase_path: Path to React codebase
|
||||
metadata: Project metadata from discovery phase
|
||||
|
||||
Returns:
|
||||
List of findings with severity and migration guidance
|
||||
"""
|
||||
findings = []
|
||||
|
||||
tech_stack = metadata.get('tech_stack', {})
|
||||
src_dir = codebase_path / 'src'
|
||||
|
||||
if not src_dir.exists():
|
||||
return findings
|
||||
|
||||
# Check for testing framework
|
||||
findings.extend(check_testing_framework(tech_stack))
|
||||
|
||||
# Check test coverage
|
||||
findings.extend(check_test_coverage(codebase_path))
|
||||
|
||||
# Analyze test distribution (unit vs integration vs E2E)
|
||||
findings.extend(analyze_test_distribution(codebase_path))
|
||||
|
||||
# Check test quality patterns
|
||||
findings.extend(check_test_quality(codebase_path))
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_testing_framework(tech_stack: Dict) -> List[Dict]:
|
||||
"""Check for modern testing setup."""
|
||||
findings = []
|
||||
|
||||
has_test_framework = tech_stack.get('vitest') or tech_stack.get('jest')
|
||||
has_testing_library = tech_stack.get('testing-library')
|
||||
|
||||
if not has_test_framework:
|
||||
findings.append({
|
||||
'severity': 'critical',
|
||||
'category': 'testing',
|
||||
'title': 'No testing framework detected',
|
||||
'current_state': 'No Vitest or Jest found',
|
||||
'target_state': 'Use Vitest (modern, fast) or Jest for testing',
|
||||
'migration_steps': [
|
||||
'Install Vitest (recommended for Vite) or Jest',
|
||||
'Install @testing-library/react',
|
||||
'Configure test setup file',
|
||||
'Add test scripts to package.json',
|
||||
'Set up coverage reporting'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
|
||||
if not has_testing_library:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'testing',
|
||||
'title': 'Testing Library not found',
|
||||
'current_state': 'No @testing-library/react detected',
|
||||
'target_state': 'Use Testing Library for user-centric testing',
|
||||
'migration_steps': [
|
||||
'Install @testing-library/react',
|
||||
'Install @testing-library/jest-dom for assertions',
|
||||
'Use render() and semantic queries (getByRole)',
|
||||
'Follow testing-library principles (test behavior, not implementation)'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_test_coverage(codebase_path: Path) -> List[Dict]:
|
||||
"""Check test coverage if available."""
|
||||
findings = []
|
||||
|
||||
# Look for coverage reports
|
||||
coverage_file = codebase_path / 'coverage' / 'coverage-summary.json'
|
||||
|
||||
if coverage_file.exists():
|
||||
try:
|
||||
with open(coverage_file, 'r') as f:
|
||||
coverage_data = json.load(f)
|
||||
total_coverage = coverage_data.get('total', {})
|
||||
line_coverage = total_coverage.get('lines', {}).get('pct', 0)
|
||||
branch_coverage = total_coverage.get('branches', {}).get('pct', 0)
|
||||
|
||||
if line_coverage < 80:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'testing',
|
||||
'title': f'Test coverage below 80% ({line_coverage:.1f}%)',
|
||||
'current_state': f'Line coverage: {line_coverage:.1f}%, Branch coverage: {branch_coverage:.1f}%',
|
||||
'target_state': 'Maintain 80%+ test coverage on all code',
|
||||
'migration_steps': [
|
||||
'Identify untested files and functions',
|
||||
'Prioritize testing critical paths (authentication, payment, data processing)',
|
||||
'Write integration tests first (70% of tests)',
|
||||
'Add unit tests for complex business logic',
|
||||
'Configure coverage thresholds in test config'
|
||||
],
|
||||
'effort': 'high',
|
||||
})
|
||||
elif line_coverage < 90:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'testing',
|
||||
'title': f'Test coverage at {line_coverage:.1f}%',
|
||||
'current_state': f'Coverage is good but could be excellent (current: {line_coverage:.1f}%)',
|
||||
'target_state': 'Aim for 90%+ coverage for production-ready code',
|
||||
'migration_steps': [
|
||||
'Identify remaining untested code paths',
|
||||
'Focus on edge cases and error handling',
|
||||
'Ensure all critical features have 100% coverage'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
findings.append({
|
||||
'severity': 'high',
|
||||
'category': 'testing',
|
||||
'title': 'No coverage report found',
|
||||
'current_state': 'Cannot find coverage/coverage-summary.json',
|
||||
'target_state': 'Generate coverage reports to track test coverage',
|
||||
'migration_steps': [
|
||||
'Configure coverage in vitest.config.ts or jest.config.js',
|
||||
'Add --coverage flag to test script',
|
||||
'Set coverage thresholds (lines: 80, branches: 75)',
|
||||
'Add coverage/ to .gitignore',
|
||||
'Review coverage reports regularly'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def analyze_test_distribution(codebase_path: Path) -> List[Dict]:
|
||||
"""Analyze testing trophy distribution."""
|
||||
findings = []
|
||||
|
||||
# Count test files by type
|
||||
unit_tests = 0
|
||||
integration_tests = 0
|
||||
e2e_tests = 0
|
||||
|
||||
test_patterns = {
|
||||
'e2e': ['e2e/', '.e2e.test.', '.e2e.spec.', 'playwright/', 'cypress/'],
|
||||
'integration': ['.test.tsx', '.test.jsx', '.spec.tsx', '.spec.jsx'], # Component tests
|
||||
'unit': ['.test.ts', '.test.js', '.spec.ts', '.spec.js'], # Logic tests
|
||||
}
|
||||
|
||||
for test_file in codebase_path.rglob('*.{test,spec}.{ts,tsx,js,jsx}'):
|
||||
test_path_str = str(test_file)
|
||||
|
||||
# E2E tests
|
||||
if any(pattern in test_path_str for pattern in test_patterns['e2e']):
|
||||
e2e_tests += 1
|
||||
# Integration tests (component tests with TSX/JSX)
|
||||
elif any(pattern in test_path_str for pattern in test_patterns['integration']):
|
||||
integration_tests += 1
|
||||
# Unit tests (pure logic, no JSX)
|
||||
else:
|
||||
unit_tests += 1
|
||||
|
||||
total_tests = unit_tests + integration_tests + e2e_tests
|
||||
|
||||
if total_tests > 0:
|
||||
int_pct = (integration_tests / total_tests) * 100
|
||||
unit_pct = (unit_tests / total_tests) * 100
|
||||
e2e_pct = (e2e_tests / total_tests) * 100
|
||||
|
||||
# Testing Trophy: 70% integration, 20% unit, 10% E2E
|
||||
if int_pct < 50: # Should be ~70%
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'testing',
|
||||
'title': 'Testing pyramid instead of testing trophy',
|
||||
'current_state': f'Distribution: {int_pct:.0f}% integration, {unit_pct:.0f}% unit, {e2e_pct:.0f}% E2E',
|
||||
'target_state': 'Testing Trophy: 70% integration, 20% unit, 10% E2E',
|
||||
'migration_steps': [
|
||||
'Write more integration tests (component + hooks + context)',
|
||||
'Test user workflows, not implementation details',
|
||||
'Reduce excessive unit tests of simple functions',
|
||||
'Keep E2E tests for critical user journeys only',
|
||||
'Use Testing Library for integration tests'
|
||||
],
|
||||
'effort': 'medium',
|
||||
})
|
||||
|
||||
if unit_pct > 40: # Should be ~20%
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'testing',
|
||||
'title': 'Too many unit tests',
|
||||
'current_state': f'{unit_pct:.0f}% unit tests (target: ~20%)',
|
||||
'target_state': 'Focus on integration tests that provide more confidence',
|
||||
'migration_steps': [
|
||||
'Review unit tests - many could be integration tests',
|
||||
'Combine related unit tests into integration tests',
|
||||
'Keep unit tests only for complex business logic',
|
||||
'Test components with their hooks and context'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def check_test_quality(codebase_path: Path) -> List[Dict]:
|
||||
"""Check for test quality anti-patterns."""
|
||||
findings = []
|
||||
|
||||
brittle_test_patterns = []
|
||||
bad_query_usage = []
|
||||
bad_naming = []
|
||||
|
||||
for test_file in codebase_path.rglob('*.{test,spec}.{ts,tsx,js,jsx}'):
|
||||
try:
|
||||
with open(test_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check for brittle tests (testing implementation)
|
||||
if 'getByTestId' in content:
|
||||
bad_query_usage.append(str(test_file))
|
||||
|
||||
# Check for testing exact counts (brittle)
|
||||
if re.search(r'expect\([^)]+\)\.toHaveLength\(\d+\)', content):
|
||||
brittle_test_patterns.append(str(test_file))
|
||||
|
||||
# Check test naming ("should X when Y")
|
||||
test_names = re.findall(r'(?:it|test)\s*\(\s*[\'"]([^\'"]+)[\'"]', content)
|
||||
for name in test_names:
|
||||
if not (name.startswith('should ') or 'when' in name.lower()):
|
||||
bad_naming.append((str(test_file), name))
|
||||
except:
|
||||
pass
|
||||
|
||||
if bad_query_usage:
|
||||
findings.append({
|
||||
'severity': 'medium',
|
||||
'category': 'testing',
|
||||
'title': f'Using getByTestId in {len(bad_query_usage)} test files',
|
||||
'current_state': 'Tests use getByTestId instead of semantic queries',
|
||||
'target_state': 'Use semantic queries: getByRole, getByLabelText, getByText',
|
||||
'migration_steps': [
|
||||
'Replace getByTestId with getByRole (most preferred)',
|
||||
'Use getByLabelText for form inputs',
|
||||
'Use getByText for user-visible content',
|
||||
'Only use getByTestId as last resort',
|
||||
'Add eslint-plugin-testing-library for enforcement'
|
||||
],
|
||||
'effort': 'medium',
|
||||
'affected_files': bad_query_usage[:5],
|
||||
})
|
||||
|
||||
if brittle_test_patterns:
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'testing',
|
||||
'title': f'Brittle test patterns in {len(brittle_test_patterns)} files',
|
||||
'current_state': 'Tests check exact counts and DOM structure',
|
||||
'target_state': 'Test user behavior and outcomes, not exact DOM structure',
|
||||
'migration_steps': [
|
||||
'Avoid testing exact element counts',
|
||||
'Focus on user-visible behavior',
|
||||
'Test functionality, not implementation',
|
||||
'Allow flexibility in DOM structure'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
if len(bad_naming) > 5: # More than 5 tests with poor naming
|
||||
findings.append({
|
||||
'severity': 'low',
|
||||
'category': 'testing',
|
||||
'title': f'{len(bad_naming)} tests with unclear naming',
|
||||
'current_state': 'Test names don\'t follow "should X when Y" pattern',
|
||||
'target_state': 'Use descriptive names: "should display error when API fails"',
|
||||
'migration_steps': [
|
||||
'Rename tests to describe expected behavior',
|
||||
'Use pattern: "should [expected behavior] when [condition]"',
|
||||
'Make tests self-documenting',
|
||||
'Tests should read like requirements'
|
||||
],
|
||||
'effort': 'low',
|
||||
})
|
||||
|
||||
return findings
|
||||
503
skills/bulletproof-react-auditor/scripts/audit_engine.py
Normal file
503
skills/bulletproof-react-auditor/scripts/audit_engine.py
Normal file
@@ -0,0 +1,503 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Bulletproof React Audit Engine
|
||||
|
||||
Orchestrates comprehensive React/TypeScript codebase analysis against Bulletproof
|
||||
React architecture principles. Generates detailed audit reports and migration plans.
|
||||
|
||||
Usage:
|
||||
python audit_engine.py /path/to/react-app --output report.md
|
||||
python audit_engine.py /path/to/react-app --format json --output report.json
|
||||
python audit_engine.py /path/to/react-app --migration-plan --output migration.md
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
import importlib.util
|
||||
|
||||
# Bulletproof React specific analyzers
|
||||
ANALYZERS = {
|
||||
'structure': 'analyzers.project_structure',
|
||||
'components': 'analyzers.component_architecture',
|
||||
'state': 'analyzers.state_management',
|
||||
'api': 'analyzers.api_layer',
|
||||
'testing': 'analyzers.testing_strategy',
|
||||
'styling': 'analyzers.styling_patterns',
|
||||
'errors': 'analyzers.error_handling',
|
||||
'performance': 'analyzers.performance_patterns',
|
||||
'security': 'analyzers.security_practices',
|
||||
'standards': 'analyzers.standards_compliance',
|
||||
}
|
||||
|
||||
|
||||
class BulletproofAuditEngine:
|
||||
"""
|
||||
Core audit engine for Bulletproof React compliance analysis.
|
||||
|
||||
Uses progressive disclosure: loads only necessary analyzers based on scope.
|
||||
"""
|
||||
|
||||
def __init__(self, codebase_path: Path, scope: Optional[List[str]] = None):
|
||||
"""
|
||||
Initialize Bulletproof React audit engine.
|
||||
|
||||
Args:
|
||||
codebase_path: Path to the React codebase to audit
|
||||
scope: Optional list of analysis categories to run
|
||||
If None, runs all analyzers.
|
||||
"""
|
||||
self.codebase_path = Path(codebase_path).resolve()
|
||||
self.scope = scope or list(ANALYZERS.keys())
|
||||
self.findings: Dict[str, List[Dict]] = {}
|
||||
self.metadata: Dict = {}
|
||||
|
||||
if not self.codebase_path.exists():
|
||||
raise FileNotFoundError(f"Codebase path does not exist: {self.codebase_path}")
|
||||
|
||||
def discover_project(self) -> Dict:
|
||||
"""
|
||||
Phase 1: Initial React project discovery (lightweight scan).
|
||||
|
||||
Returns:
|
||||
Dictionary containing React project metadata
|
||||
"""
|
||||
print("🔍 Phase 1: Discovering React project structure...")
|
||||
|
||||
metadata = {
|
||||
'path': str(self.codebase_path),
|
||||
'scan_time': datetime.now().isoformat(),
|
||||
'is_react': self._detect_react(),
|
||||
'tech_stack': self._detect_tech_stack(),
|
||||
'structure_type': self._detect_structure_type(),
|
||||
'total_files': self._count_files(),
|
||||
'total_lines': self._count_lines(),
|
||||
'git_info': self._get_git_info(),
|
||||
}
|
||||
|
||||
if not metadata['is_react']:
|
||||
print("⚠️ Warning: This does not appear to be a React project!")
|
||||
print(" Bulletproof React audit is designed for React applications.")
|
||||
|
||||
self.metadata = metadata
|
||||
return metadata
|
||||
|
||||
def _detect_react(self) -> bool:
|
||||
"""Check if this is a React project."""
|
||||
pkg_json = self.codebase_path / 'package.json'
|
||||
if not pkg_json.exists():
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(pkg_json, 'r') as f:
|
||||
pkg = json.load(f)
|
||||
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
||||
return 'react' in deps or 'react-dom' in deps
|
||||
except:
|
||||
return False
|
||||
|
||||
def _detect_tech_stack(self) -> Dict[str, bool]:
|
||||
"""Detect React ecosystem tools and libraries."""
|
||||
pkg_json = self.codebase_path / 'package.json'
|
||||
tech_stack = {}
|
||||
|
||||
if pkg_json.exists():
|
||||
try:
|
||||
with open(pkg_json, 'r') as f:
|
||||
pkg = json.load(f)
|
||||
deps = {**pkg.get('dependencies', {}), **pkg.get('devDependencies', {})}
|
||||
|
||||
# Core
|
||||
tech_stack['react'] = 'react' in deps
|
||||
tech_stack['typescript'] = 'typescript' in deps or (self.codebase_path / 'tsconfig.json').exists()
|
||||
|
||||
# Build tools
|
||||
tech_stack['vite'] = 'vite' in deps
|
||||
tech_stack['create-react-app'] = 'react-scripts' in deps
|
||||
tech_stack['next'] = 'next' in deps
|
||||
|
||||
# State management
|
||||
tech_stack['redux'] = 'redux' in deps or '@reduxjs/toolkit' in deps
|
||||
tech_stack['zustand'] = 'zustand' in deps
|
||||
tech_stack['jotai'] = 'jotai' in deps
|
||||
tech_stack['mobx'] = 'mobx' in deps
|
||||
|
||||
# Data fetching
|
||||
tech_stack['react-query'] = '@tanstack/react-query' in deps or 'react-query' in deps
|
||||
tech_stack['swr'] = 'swr' in deps
|
||||
tech_stack['apollo'] = '@apollo/client' in deps
|
||||
tech_stack['rtk-query'] = '@reduxjs/toolkit' in deps
|
||||
|
||||
# Forms
|
||||
tech_stack['react-hook-form'] = 'react-hook-form' in deps
|
||||
tech_stack['formik'] = 'formik' in deps
|
||||
|
||||
# Styling
|
||||
tech_stack['tailwind'] = 'tailwindcss' in deps or (self.codebase_path / 'tailwind.config.js').exists()
|
||||
tech_stack['styled-components'] = 'styled-components' in deps
|
||||
tech_stack['emotion'] = '@emotion/react' in deps
|
||||
tech_stack['chakra-ui'] = '@chakra-ui/react' in deps
|
||||
tech_stack['mui'] = '@mui/material' in deps
|
||||
tech_stack['radix-ui'] = any('@radix-ui' in dep for dep in deps.keys())
|
||||
|
||||
# Testing
|
||||
tech_stack['vitest'] = 'vitest' in deps
|
||||
tech_stack['jest'] = 'jest' in deps
|
||||
tech_stack['testing-library'] = '@testing-library/react' in deps
|
||||
tech_stack['playwright'] = '@playwright/test' in deps
|
||||
tech_stack['cypress'] = 'cypress' in deps
|
||||
|
||||
# Routing
|
||||
tech_stack['react-router'] = 'react-router-dom' in deps
|
||||
|
||||
# Error tracking
|
||||
tech_stack['sentry'] = '@sentry/react' in deps
|
||||
|
||||
# Code quality
|
||||
tech_stack['eslint'] = 'eslint' in deps
|
||||
tech_stack['prettier'] = 'prettier' in deps
|
||||
tech_stack['husky'] = 'husky' in deps
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return {k: v for k, v in tech_stack.items() if v}
|
||||
|
||||
def _detect_structure_type(self) -> str:
|
||||
"""Determine project structure pattern (feature-based vs flat)."""
|
||||
src_dir = self.codebase_path / 'src'
|
||||
if not src_dir.exists():
|
||||
return 'no_src_directory'
|
||||
|
||||
features_dir = src_dir / 'features'
|
||||
components_dir = src_dir / 'components'
|
||||
app_dir = src_dir / 'app'
|
||||
|
||||
# Count files in different locations
|
||||
features_files = len(list(features_dir.rglob('*.{js,jsx,ts,tsx}'))) if features_dir.exists() else 0
|
||||
components_files = len(list(components_dir.rglob('*.{js,jsx,ts,tsx}'))) if components_dir.exists() else 0
|
||||
|
||||
if features_dir.exists() and app_dir.exists():
|
||||
if features_files > components_files * 2:
|
||||
return 'feature_based'
|
||||
else:
|
||||
return 'mixed'
|
||||
elif features_dir.exists():
|
||||
return 'partial_feature_based'
|
||||
else:
|
||||
return 'flat'
|
||||
|
||||
def _count_files(self) -> int:
|
||||
"""Count total files in React codebase."""
|
||||
exclude_dirs = {'.git', 'node_modules', 'dist', 'build', '.next', 'out', 'coverage'}
|
||||
count = 0
|
||||
|
||||
for path in self.codebase_path.rglob('*'):
|
||||
if path.is_file() and not any(excluded in path.parts for excluded in exclude_dirs):
|
||||
count += 1
|
||||
|
||||
return count
|
||||
|
||||
def _count_lines(self) -> int:
|
||||
"""Count total lines of code in React files."""
|
||||
exclude_dirs = {'.git', 'node_modules', 'dist', 'build', '.next', 'out', 'coverage'}
|
||||
code_extensions = {'.js', '.jsx', '.ts', '.tsx'}
|
||||
total_lines = 0
|
||||
|
||||
for path in self.codebase_path.rglob('*'):
|
||||
if (path.is_file() and
|
||||
path.suffix in code_extensions and
|
||||
not any(excluded in path.parts for excluded in exclude_dirs)):
|
||||
try:
|
||||
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
total_lines += sum(1 for line in f if line.strip() and not line.strip().startswith(('//', '#', '/*', '*')))
|
||||
except:
|
||||
pass
|
||||
|
||||
return total_lines
|
||||
|
||||
def _get_git_info(self) -> Optional[Dict]:
|
||||
"""Get git repository information."""
|
||||
git_dir = self.codebase_path / '.git'
|
||||
if not git_dir.exists():
|
||||
return None
|
||||
|
||||
try:
|
||||
import subprocess
|
||||
result = subprocess.run(
|
||||
['git', '-C', str(self.codebase_path), 'log', '--oneline', '-10'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
commit_count = subprocess.run(
|
||||
['git', '-C', str(self.codebase_path), 'rev-list', '--count', 'HEAD'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=5
|
||||
)
|
||||
|
||||
return {
|
||||
'is_git_repo': True,
|
||||
'recent_commits': result.stdout.strip().split('\n') if result.returncode == 0 else [],
|
||||
'total_commits': int(commit_count.stdout.strip()) if commit_count.returncode == 0 else 0,
|
||||
}
|
||||
except:
|
||||
return {'is_git_repo': True, 'error': 'Could not read git info'}
|
||||
|
||||
def run_analysis(self, phase: str = 'full') -> Dict:
|
||||
"""
|
||||
Phase 2: Deep Bulletproof React analysis using specialized analyzers.
|
||||
|
||||
Args:
|
||||
phase: 'quick' for lightweight scan, 'full' for comprehensive analysis
|
||||
|
||||
Returns:
|
||||
Dictionary containing all findings
|
||||
"""
|
||||
print(f"🔬 Phase 2: Running {phase} Bulletproof React analysis...")
|
||||
|
||||
for category in self.scope:
|
||||
if category not in ANALYZERS:
|
||||
print(f"⚠️ Unknown analyzer category: {category}, skipping...")
|
||||
continue
|
||||
|
||||
print(f" Analyzing {category}...")
|
||||
analyzer_findings = self._run_analyzer(category)
|
||||
if analyzer_findings:
|
||||
self.findings[category] = analyzer_findings
|
||||
|
||||
return self.findings
|
||||
|
||||
def _run_analyzer(self, category: str) -> List[Dict]:
|
||||
"""
|
||||
Run a specific Bulletproof React analyzer module.
|
||||
|
||||
Args:
|
||||
category: Analyzer category name
|
||||
|
||||
Returns:
|
||||
List of findings from the analyzer
|
||||
"""
|
||||
module_path = ANALYZERS.get(category)
|
||||
if not module_path:
|
||||
return []
|
||||
|
||||
try:
|
||||
# Import analyzer module dynamically
|
||||
analyzer_file = Path(__file__).parent / f"{module_path.replace('.', '/')}.py"
|
||||
|
||||
if not analyzer_file.exists():
|
||||
print(f" ⚠️ Analyzer not yet implemented: {category}")
|
||||
return []
|
||||
|
||||
spec = importlib.util.spec_from_file_location(module_path, analyzer_file)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
# Each analyzer should have an analyze() function
|
||||
if hasattr(module, 'analyze'):
|
||||
return module.analyze(self.codebase_path, self.metadata)
|
||||
else:
|
||||
print(f" ⚠️ Analyzer missing analyze() function: {category}")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Error running analyzer {category}: {e}")
|
||||
return []
|
||||
|
||||
def calculate_scores(self) -> Dict[str, float]:
|
||||
"""
|
||||
Calculate Bulletproof React compliance scores for each category.
|
||||
|
||||
Returns:
|
||||
Dictionary of scores (0-100 scale)
|
||||
"""
|
||||
scores = {}
|
||||
|
||||
# Calculate score for each category based on findings severity
|
||||
for category, findings in self.findings.items():
|
||||
if not findings:
|
||||
scores[category] = 100.0
|
||||
continue
|
||||
|
||||
# Weighted scoring based on severity
|
||||
severity_weights = {'critical': 15, 'high': 8, 'medium': 3, 'low': 1}
|
||||
total_weight = sum(severity_weights.get(f.get('severity', 'low'), 1) for f in findings)
|
||||
|
||||
# Score decreases based on weighted issues
|
||||
penalty = min(total_weight * 2, 100) # Each point = 2% penalty
|
||||
scores[category] = max(0, 100 - penalty)
|
||||
|
||||
# Overall score is weighted average
|
||||
if scores:
|
||||
scores['overall'] = sum(scores.values()) / len(scores)
|
||||
else:
|
||||
scores['overall'] = 100.0
|
||||
|
||||
return scores
|
||||
|
||||
def calculate_grade(self, score: float) -> str:
|
||||
"""Convert score to letter grade."""
|
||||
if score >= 90: return 'A'
|
||||
if score >= 80: return 'B'
|
||||
if score >= 70: return 'C'
|
||||
if score >= 60: return 'D'
|
||||
return 'F'
|
||||
|
||||
def generate_summary(self) -> Dict:
|
||||
"""
|
||||
Generate executive summary of Bulletproof React audit results.
|
||||
|
||||
Returns:
|
||||
Summary dictionary
|
||||
"""
|
||||
critical_count = sum(
|
||||
1 for findings in self.findings.values()
|
||||
for f in findings
|
||||
if f.get('severity') == 'critical'
|
||||
)
|
||||
|
||||
high_count = sum(
|
||||
1 for findings in self.findings.values()
|
||||
for f in findings
|
||||
if f.get('severity') == 'high'
|
||||
)
|
||||
|
||||
scores = self.calculate_scores()
|
||||
overall_score = scores.get('overall', 0)
|
||||
|
||||
# Estimate migration effort in person-days
|
||||
effort_map = {'low': 0.5, 'medium': 2, 'high': 5}
|
||||
total_effort = sum(
|
||||
effort_map.get(f.get('effort', 'medium'), 2)
|
||||
for findings in self.findings.values()
|
||||
for f in findings
|
||||
)
|
||||
|
||||
return {
|
||||
'compliance_score': round(overall_score, 1),
|
||||
'grade': self.calculate_grade(overall_score),
|
||||
'category_scores': {k: round(v, 1) for k, v in scores.items() if k != 'overall'},
|
||||
'critical_issues': critical_count,
|
||||
'high_issues': high_count,
|
||||
'total_issues': sum(len(findings) for findings in self.findings.values()),
|
||||
'migration_effort_days': round(total_effort, 1),
|
||||
'structure_type': self.metadata.get('structure_type', 'unknown'),
|
||||
'metadata': self.metadata,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for CLI usage."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Bulletproof React audit tool for React/TypeScript applications',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'codebase',
|
||||
type=str,
|
||||
help='Path to the React codebase to audit'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--scope',
|
||||
type=str,
|
||||
help='Comma-separated list of analysis categories (structure,components,state,api,testing,styling,errors,performance,security,standards)',
|
||||
default=None
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--phase',
|
||||
type=str,
|
||||
choices=['quick', 'full'],
|
||||
default='full',
|
||||
help='Analysis depth: quick (Phase 1 only) or full (Phase 1 + 2)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
type=str,
|
||||
choices=['markdown', 'json', 'html'],
|
||||
default='markdown',
|
||||
help='Output format for the report'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--output',
|
||||
type=str,
|
||||
help='Output file path (default: stdout)',
|
||||
default=None
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--migration-plan',
|
||||
action='store_true',
|
||||
help='Generate migration plan in addition to audit report'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Parse scope
|
||||
scope = args.scope.split(',') if args.scope else None
|
||||
|
||||
# Initialize engine
|
||||
try:
|
||||
engine = BulletproofAuditEngine(args.codebase, scope=scope)
|
||||
except FileNotFoundError as e:
|
||||
print(f"❌ Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Run audit
|
||||
print("🚀 Starting Bulletproof React audit...")
|
||||
print(f" Codebase: {args.codebase}")
|
||||
print(f" Scope: {scope or 'all'}")
|
||||
print(f" Phase: {args.phase}")
|
||||
print()
|
||||
|
||||
# Phase 1: Discovery
|
||||
metadata = engine.discover_project()
|
||||
if metadata['is_react']:
|
||||
print(f" React detected: ✅")
|
||||
print(f" TypeScript: {'✅' if metadata['tech_stack'].get('typescript') else '❌'}")
|
||||
print(f" Structure type: {metadata['structure_type']}")
|
||||
print(f" Files: {metadata['total_files']}")
|
||||
print(f" Lines of code: {metadata['total_lines']:,}")
|
||||
else:
|
||||
print(f" React detected: ❌")
|
||||
print(" Continuing audit anyway...")
|
||||
print()
|
||||
|
||||
# Phase 2: Analysis (if not quick mode)
|
||||
if args.phase == 'full':
|
||||
findings = engine.run_analysis()
|
||||
|
||||
# Generate summary
|
||||
summary = engine.generate_summary()
|
||||
|
||||
# Output results
|
||||
print()
|
||||
print("📊 Bulletproof React Audit Complete!")
|
||||
print(f" Compliance score: {summary['compliance_score']}/100 (Grade: {summary['grade']})")
|
||||
print(f" Critical issues: {summary['critical_issues']}")
|
||||
print(f" High issues: {summary['high_issues']}")
|
||||
print(f" Total issues: {summary['total_issues']}")
|
||||
print(f" Estimated migration effort: {summary['migration_effort_days']} person-days")
|
||||
print()
|
||||
|
||||
# Generate report (to be implemented in report_generator.py)
|
||||
if args.output:
|
||||
print(f"📝 Report generation will be implemented in report_generator.py")
|
||||
print(f" Format: {args.format}")
|
||||
print(f" Output: {args.output}")
|
||||
if args.migration_plan:
|
||||
print(f" Migration plan: {args.output.replace('.md', '_migration.md')}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user