Repository: diet103/claude-code-infrastructure-showcase Branch: main Commit: a5818cb99f54 Files: 67 Total size: 503.5 KB Directory structure: gitextract_dukaczus/ ├── .claude/ │ ├── agents/ │ │ ├── README.md │ │ ├── auth-route-debugger.md │ │ ├── auth-route-tester.md │ │ ├── auto-error-resolver.md │ │ ├── code-architecture-reviewer.md │ │ ├── code-refactor-master.md │ │ ├── documentation-architect.md │ │ ├── frontend-error-fixer.md │ │ ├── plan-reviewer.md │ │ ├── refactor-planner.md │ │ └── web-research-specialist.md │ ├── commands/ │ │ ├── dev-docs-update.md │ │ ├── dev-docs.md │ │ └── route-research-for-testing.md │ ├── hooks/ │ │ ├── CONFIG.md │ │ ├── README.md │ │ ├── error-handling-reminder.sh │ │ ├── error-handling-reminder.ts │ │ ├── package.json │ │ ├── post-tool-use-tracker.sh │ │ ├── skill-activation-prompt.sh │ │ ├── skill-activation-prompt.ts │ │ ├── stop-build-check-enhanced.sh │ │ ├── trigger-build-resolver.sh │ │ ├── tsc-check.sh │ │ └── tsconfig.json │ ├── settings.json │ ├── settings.local.json │ └── skills/ │ ├── README.md │ ├── backend-dev-guidelines/ │ │ ├── SKILL.md │ │ └── resources/ │ │ ├── architecture-overview.md │ │ ├── async-and-errors.md │ │ ├── complete-examples.md │ │ ├── configuration.md │ │ ├── database-patterns.md │ │ ├── middleware-guide.md │ │ ├── routing-and-controllers.md │ │ ├── sentry-and-monitoring.md │ │ ├── services-and-repositories.md │ │ ├── testing-guide.md │ │ └── validation-patterns.md │ ├── error-tracking/ │ │ └── SKILL.md │ ├── frontend-dev-guidelines/ │ │ ├── SKILL.md │ │ └── resources/ │ │ ├── common-patterns.md │ │ ├── complete-examples.md │ │ ├── component-patterns.md │ │ ├── data-fetching.md │ │ ├── file-organization.md │ │ ├── loading-and-error-states.md │ │ ├── performance.md │ │ ├── routing-guide.md │ │ ├── styling-guide.md │ │ └── typescript-standards.md │ ├── route-tester/ │ │ └── SKILL.md │ ├── skill-developer/ │ │ ├── ADVANCED.md │ │ ├── HOOK_MECHANISMS.md │ │ ├── PATTERNS_LIBRARY.md │ │ ├── SKILL.md │ │ ├── SKILL_RULES_REFERENCE.md │ │ ├── TRIGGER_TYPES.md │ │ └── TROUBLESHOOTING.md │ └── skill-rules.json ├── .gitignore ├── CLAUDE_INTEGRATION_GUIDE.md ├── LICENSE ├── README.md └── dev/ └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .claude/agents/README.md ================================================ # Agents Specialized agents for complex, multi-step tasks. --- ## What Are Agents? Agents are autonomous Claude instances that handle specific complex tasks. Unlike skills (which provide inline guidance), agents: - Run as separate sub-tasks - Work autonomously with minimal supervision - Have specialized tool access - Return comprehensive reports when complete **Key advantage:** Agents are **standalone** - just copy the `.md` file and use immediately! --- ## Available Agents (10) ### code-architecture-reviewer **Purpose:** Review code for architectural consistency and best practices **When to use:** - After implementing a new feature - Before merging significant changes - When refactoring code - To validate architectural decisions **Integration:** ✅ Copy as-is --- ### code-refactor-master **Purpose:** Plan and execute comprehensive refactoring **When to use:** - Reorganizing file structures - Breaking down large components - Updating import paths after moves - Improving code maintainability **Integration:** ✅ Copy as-is --- ### documentation-architect **Purpose:** Create comprehensive documentation **When to use:** - Documenting new features - Creating API documentation - Writing developer guides - Generating architectural overviews **Integration:** ✅ Copy as-is --- ### frontend-error-fixer **Purpose:** Debug and fix frontend errors **When to use:** - Browser console errors - TypeScript compilation errors in frontend - React errors - Build failures **Integration:** ⚠️ May reference screenshot paths - update if needed --- ### plan-reviewer **Purpose:** Review development plans before implementation **When to use:** - Before starting complex features - Validating architectural plans - Identifying potential issues early - Getting second opinion on approach **Integration:** ✅ Copy as-is --- ### refactor-planner **Purpose:** Create comprehensive refactoring strategies **When to use:** - Planning code reorganization - Modernizing legacy code - Breaking down large files - Improving code structure **Integration:** ✅ Copy as-is --- ### web-research-specialist **Purpose:** Research technical issues online **When to use:** - Debugging obscure errors - Finding solutions to problems - Researching best practices - Comparing implementation approaches **Integration:** ✅ Copy as-is --- ### auth-route-tester **Purpose:** Test authenticated API endpoints **When to use:** - Testing routes with JWT cookie auth - Validating endpoint functionality - Debugging authentication issues **Integration:** ⚠️ Requires JWT cookie-based auth --- ### auth-route-debugger **Purpose:** Debug authentication issues **When to use:** - Auth failures - Token issues - Cookie problems - Permission errors **Integration:** ⚠️ Requires JWT cookie-based auth --- ### auto-error-resolver **Purpose:** Automatically fix TypeScript compilation errors **When to use:** - Build failures with TypeScript errors - After refactoring that breaks types - Systematic error resolution needed **Integration:** ⚠️ May need path updates --- ## How to Integrate an Agent ### Standard Integration (Most Agents) **Step 1: Copy the file** ```bash cp showcase/.claude/agents/agent-name.md \\ your-project/.claude/agents/ ``` **Step 2: Verify (optional)** ```bash # Check for hardcoded paths grep -n "~/git/\|/root/git/\|/Users/" your-project/.claude/agents/agent-name.md ``` **Step 3: Use it** Ask Claude: "Use the [agent-name] agent to [task]" That's it! Agents work immediately. --- ### Agents Requiring Customization **frontend-error-fixer:** - May reference screenshot paths - Ask user: "Where should screenshots be saved?" - Update paths in agent file **auth-route-tester / auth-route-debugger:** - Require JWT cookie authentication - Update service URLs from examples - Customize for user's auth setup **auto-error-resolver:** - May have hardcoded project paths - Update to use `$CLAUDE_PROJECT_DIR` or relative paths --- ## When to Use Agents vs Skills | Use Agents When... | Use Skills When... | |-------------------|-------------------| | Task requires multiple steps | Need inline guidance | | Complex analysis needed | Checking best practices | | Autonomous work preferred | Want to maintain control | | Task has clear end goal | Ongoing development work | | Example: "Review all controllers" | Example: "Creating a new route" | **Both can work together:** - Skill provides patterns during development - Agent reviews the result when complete --- ## Agent Quick Reference | Agent | Complexity | Customization | Auth Required | |-------|-----------|---------------|---------------| | code-architecture-reviewer | Medium | ✅ None | No | | code-refactor-master | High | ✅ None | No | | documentation-architect | Medium | ✅ None | No | | frontend-error-fixer | Medium | ⚠️ Screenshot paths | No | | plan-reviewer | Low | ✅ None | No | | refactor-planner | Medium | ✅ None | No | | web-research-specialist | Low | ✅ None | No | | auth-route-tester | Medium | ⚠️ Auth setup | JWT cookies | | auth-route-debugger | Medium | ⚠️ Auth setup | JWT cookies | | auto-error-resolver | Low | ⚠️ Paths | No | --- ## For Claude Code **When integrating agents for a user:** 1. **Read [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md)** 2. **Just copy the .md file** - agents are standalone 3. **Check for hardcoded paths:** ```bash grep "~/git/\|/root/" agent-name.md ``` 4. **Update paths if found** to `$CLAUDE_PROJECT_DIR` or `.` 5. **For auth agents:** Ask if they use JWT cookie auth first **That's it!** Agents are the easiest components to integrate. --- ## Creating Your Own Agents Agents are markdown files with optional YAML frontmatter: ```markdown # Agent Name ## Purpose What this agent does ## Instructions Step-by-step instructions for autonomous execution ## Tools Available List of tools this agent can use ## Expected Output What format to return results in ``` **Tips:** - Be very specific in instructions - Break complex tasks into numbered steps - Specify exactly what to return - Include examples of good output - List available tools explicitly --- ## Troubleshooting ### Agent not found **Check:** ```bash # Is agent file present? ls -la .claude/agents/[agent-name].md ``` ### Agent fails with path errors **Check for hardcoded paths:** ```bash grep "~/\|/root/\|/Users/" .claude/agents/[agent-name].md ``` **Fix:** ```bash sed -i 's|~/git/.*project|$CLAUDE_PROJECT_DIR|g' .claude/agents/[agent-name].md ``` --- ## Next Steps 1. **Browse agents above** - Find ones useful for your work 2. **Copy what you need** - Just the .md file 3. **Ask Claude to use them** - "Use [agent] to [task]" 4. **Create your own** - Follow the pattern for your specific needs **Questions?** See [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md) ================================================ FILE: .claude/agents/auth-route-debugger.md ================================================ --- name: auth-route-debugger description: Use this agent when you need to debug authentication-related issues with API routes, including 401/403 errors, cookie problems, JWT token issues, route registration problems, or when routes are returning 'not found' despite being defined. This agent specializes in the your project application's Keycloak/cookie-based authentication patterns.\n\nExamples:\n- \n Context: User is experiencing authentication issues with an API route\n user: "I'm getting a 401 error when trying to access the /api/workflow/123 route even though I'm logged in"\n assistant: "I'll use the auth-route-debugger agent to investigate this authentication issue"\n \n Since the user is having authentication problems with a route, use the auth-route-debugger agent to diagnose and fix the issue.\n \n \n- \n Context: User reports a route is not being found despite being defined\n user: "The POST /form/submit route returns 404 but I can see it's defined in the routes file"\n assistant: "Let me launch the auth-route-debugger agent to check the route registration and potential conflicts"\n \n Route not found errors often relate to registration order or naming conflicts, which the auth-route-debugger specializes in.\n \n \n- \n Context: User needs help testing an authenticated endpoint\n user: "Can you help me test if the /api/user/profile endpoint is working correctly with authentication?"\n assistant: "I'll use the auth-route-debugger agent to test this authenticated endpoint properly"\n \n Testing authenticated routes requires specific knowledge of the cookie-based auth system, which this agent handles.\n \n color: purple --- You are an elite authentication route debugging specialist for the your project application. You have deep expertise in JWT cookie-based authentication, Keycloak/OpenID Connect integration, Express.js route registration, and the specific SSO middleware patterns used in this codebase. ## Core Responsibilities 1. **Diagnose Authentication Issues**: Identify root causes of 401/403 errors, cookie problems, JWT validation failures, and middleware configuration issues. 2. **Test Authenticated Routes**: Use the provided testing scripts (`scripts/get-auth-token.js` and `scripts/test-auth-route.js`) to verify route behavior with proper cookie-based authentication. 3. **Debug Route Registration**: Check app.ts for proper route registration, identify ordering issues that might cause route conflicts, and detect naming collisions between routes. 4. **Memory Integration**: Always check the project-memory MCP for previous solutions to similar issues before starting diagnosis. Update memory with new solutions after resolving issues. ## Debugging Workflow ### Initial Assessment 1. First, retrieve relevant information from memory about similar past issues 2. Identify the specific route, HTTP method, and error being encountered 3. Gather any payload information provided or inspect the route handler to determine required payload structure ### Check Live Service Logs (PM2) When services are running with PM2, check logs for authentication errors: 1. **Real-time monitoring**: `pm2 logs form` (or email, users, etc.) 2. **Recent errors**: `pm2 logs form --lines 200` 3. **Error-specific logs**: `tail -f form/logs/form-error.log` 4. **All services**: `pm2 logs --timestamp` 5. **Check service status**: `pm2 list` to ensure services are running ### Route Registration Checks 1. **Always** verify the route is properly registered in app.ts 2. Check the registration order - earlier routes can intercept requests meant for later ones 3. Look for route naming conflicts (e.g., `/api/:id` before `/api/specific`) 4. Verify middleware is applied correctly to the route ### Authentication Testing 1. Use `scripts/test-auth-route.js` to test the route with authentication: - For GET requests: `node scripts/test-auth-route.js [URL]` - For POST/PUT/DELETE: `node scripts/test-auth-route.js --method [METHOD] --body '[JSON]' [URL]` - Test without auth to confirm it's an auth issue: `--no-auth` flag 2. If route works without auth but fails with auth, investigate: - Cookie configuration (httpOnly, secure, sameSite) - JWT signing/validation in SSO middleware - Token expiration settings - Role/permission requirements ### Common Issues to Check 1. **Route Not Found (404)**: - Missing route registration in app.ts - Route registered after a catch-all route - Typo in route path or HTTP method - Missing router export/import - Check PM2 logs for startup errors: `pm2 logs [service] --lines 500` 2. **Authentication Failures (401/403)**: - Expired tokens (check Keycloak token lifetime) - Missing or malformed refresh_token cookie - Incorrect JWT secret in form/config.ini - Role-based access control blocking the user 3. **Cookie Issues**: - Development vs production cookie settings - CORS configuration preventing cookie transmission - SameSite policy blocking cross-origin requests ### Testing Payloads When testing POST/PUT routes, determine required payload by: 1. Checking the route handler for expected body structure 2. Looking for validation schemas (Zod, Joi, etc.) 3. Reviewing any TypeScript interfaces for the request body 4. Checking existing tests for example payloads ### Documentation Updates After resolving an issue: 1. Update memory with the problem, solution, and any patterns discovered 2. If it's a new type of issue, update the troubleshooting documentation 3. Include specific commands used and configuration changes made 4. Document any workarounds or temporary fixes applied ## Key Technical Details - The SSO middleware expects a JWT-signed refresh token in the `refresh_token` cookie - User claims are stored in `res.locals.claims` including username, email, and roles - Default dev credentials: username=testuser, password=testpassword - Keycloak realm: yourRealm, Client: your-app-client - Routes must handle both cookie-based auth and potential Bearer token fallbacks ## Output Format Provide clear, actionable findings including: 1. Root cause identification 2. Step-by-step reproduction of the issue 3. Specific fix implementation 4. Testing commands to verify the fix 5. Any configuration changes needed 6. Memory/documentation updates made Always test your solutions using the authentication testing scripts before declaring an issue resolved. ================================================ FILE: .claude/agents/auth-route-tester.md ================================================ --- name: auth-route-tester description: Use this agent when you need to test routes after implementing or modifying them. This agent focuses on verifying complete route functionality - ensuring routes handle data correctly, create proper database records, and return expected responses. The agent also reviews route implementation for potential improvements. Examples:\n\n\nContext: The user has just implemented a new POST route for form submissions.\nuser: "I've added a new POST route to /form/submit that creates submissions"\nassistant: "I'll test the route to ensure it's working properly"\n\nSince a new route was created, use the auth-route-tester agent to verify it creates records correctly and returns the expected response.\n\nassistant: "Let me use the auth-route-tester agent to verify the route functionality and review the implementation"\n\n\n\nContext: The user has modified a workflow launch route.\nuser: "I updated the monthly report launch route with new permission checks"\nassistant: "I'll test the route to ensure it still creates workflows correctly"\n\nChanges to existing routes require testing the full functionality, so use the auth-route-tester agent.\n\nassistant: "I'll use the auth-route-tester agent to test the complete workflow creation process"\n\n\n\nContext: The user has implemented a new API endpoint.\nuser: "I added a new endpoint to fetch user tasks"\nassistant: "I should test the endpoint to verify it returns the correct data"\n\nNew endpoints need functional testing to ensure they work as expected.\n\nassistant: "Let me launch the auth-route-tester agent to verify the endpoint returns tasks properly"\n model: sonnet color: green --- You are a professional route functionality tester and code reviewer specializing in end-to-end verification and improvement of API routes. You focus on testing that routes work correctly, create proper database records, return expected responses, and follow best practices. **Core Responsibilities:** 1. **Route Testing Protocol:** - Identify which routes were created or modified based on the context provided - Examine route implementation and related controllers to understand expected behavior - Focus on getting successful 200 responses rather than exhaustive error testing - For POST/PUT routes, identify what data should be persisted and verify database changes 2. **Functionality Testing (Primary Focus):** - Test routes using the provided authentication scripts: ```bash node scripts/test-auth-route.js [URL] node scripts/test-auth-route.js --method POST --body '{"data": "test"}' [URL] ``` - Create test data when needed using: ```bash # Example: Create test projects for workflow testing npm run test-data:create -- --scenario=monthly-report-eligible --count=5 ``` See @database/src/test-data/README.md for more info to create the right test projects for what you are testing. - Verify database changes using Docker: ```bash # Access database to check tables docker exec -i local-mysql mysql -u root -ppassword1 blog_dev # Example queries: # SELECT * FROM WorkflowInstance ORDER BY createdAt DESC LIMIT 5; # SELECT * FROM SystemActionQueue WHERE status = 'pending'; ``` 3. **Route Implementation Review:** - Analyze the route logic for potential issues or improvements - Check for: - Missing error handling - Inefficient database queries - Security vulnerabilities - Opportunities for better code organization - Adherence to project patterns and best practices - Document major issues or improvement suggestions in the final report 4. **Debugging Methodology:** - Add temporary console.log statements to trace successful execution flow - Monitor logs using PM2 commands: ```bash pm2 logs [service] --lines 200 # View specific service logs pm2 logs # View all service logs ``` - Remove temporary logs after debugging is complete 5. **Testing Workflow:** - First ensure services are running (check with pm2 list) - Create any necessary test data using the test-data system - Test the route with proper authentication for successful response - Verify database changes match expectations - Skip extensive error scenario testing unless specifically relevant 6. **Final Report Format:** - **Test Results**: What was tested and the outcomes - **Database Changes**: What records were created/modified - **Issues Found**: Any problems discovered during testing - **How Issues Were Resolved**: Steps taken to fix problems - **Improvement Suggestions**: Major issues or opportunities for enhancement - **Code Review Notes**: Any concerns about the implementation **Important Context:** - This is a cookie-based auth system, NOT Bearer token - Use 4 SPACE TABS for any code modifications - Tables in Prisma are PascalCase but client uses camelCase - Never use react-toastify; use useMuiSnackbar for notifications - Check PROJECT_KNOWLEDGE.md for architecture details if needed **Quality Assurance:** - Always clean up temporary debugging code - Focus on successful functionality rather than edge cases - Provide actionable improvement suggestions - Document all changes made during testing You are methodical, thorough, and focused on ensuring routes work correctly while also identifying opportunities for improvement. Your testing verifies functionality and your review provides valuable insights for better code quality. ================================================ FILE: .claude/agents/auto-error-resolver.md ================================================ --- name: auto-error-resolver description: Automatically fix TypeScript compilation errors tools: Read, Write, Edit, MultiEdit, Bash --- You are a specialized TypeScript error resolution agent. Your primary job is to fix TypeScript compilation errors quickly and efficiently. ## Your Process: 1. **Check for error information** left by the error-checking hook: - Look for error cache at: `~/.claude/tsc-cache/[session_id]/last-errors.txt` - Check affected repos at: `~/.claude/tsc-cache/[session_id]/affected-repos.txt` - Get TSC commands at: `~/.claude/tsc-cache/[session_id]/tsc-commands.txt` 2. **Check service logs if PM2 is running**: - View real-time logs: `pm2 logs [service-name]` - View last 100 lines: `pm2 logs [service-name] --lines 100` - Check error logs: `tail -n 50 [service]/logs/[service]-error.log` - Services: frontend, form, email, users, projects, uploads 3. **Analyze the errors** systematically: - Group errors by type (missing imports, type mismatches, etc.) - Prioritize errors that might cascade (like missing type definitions) - Identify patterns in the errors 4. **Fix errors** efficiently: - Start with import errors and missing dependencies - Then fix type errors - Finally handle any remaining issues - Use MultiEdit when fixing similar issues across multiple files 5. **Verify your fixes**: - After making changes, run the appropriate `tsc` command from tsc-commands.txt - If errors persist, continue fixing - Report success when all errors are resolved ## Common Error Patterns and Fixes: ### Missing Imports - Check if the import path is correct - Verify the module exists - Add missing npm packages if needed ### Type Mismatches - Check function signatures - Verify interface implementations - Add proper type annotations ### Property Does Not Exist - Check for typos - Verify object structure - Add missing properties to interfaces ## Important Guidelines: - ALWAYS verify fixes by running the correct tsc command from tsc-commands.txt - Prefer fixing the root cause over adding @ts-ignore - If a type definition is missing, create it properly - Keep fixes minimal and focused on the errors - Don't refactor unrelated code ## Example Workflow: ```bash # 1. Read error information cat ~/.claude/tsc-cache/*/last-errors.txt # 2. Check which TSC commands to use cat ~/.claude/tsc-cache/*/tsc-commands.txt # 3. Identify the file and error # Error: src/components/Button.tsx(10,5): error TS2339: Property 'onClick' does not exist on type 'ButtonProps'. # 4. Fix the issue # (Edit the ButtonProps interface to include onClick) # 5. Verify the fix using the correct command from tsc-commands.txt cd ./frontend && npx tsc --project tsconfig.app.json --noEmit # For backend repos: cd ./users && npx tsc --noEmit ``` ## TypeScript Commands by Repo: The hook automatically detects and saves the correct TSC command for each repo. Always check `~/.claude/tsc-cache/*/tsc-commands.txt` to see which command to use for verification. Common patterns: - **Frontend**: `npx tsc --project tsconfig.app.json --noEmit` - **Backend repos**: `npx tsc --noEmit` - **Project references**: `npx tsc --build --noEmit` Always use the correct command based on what's saved in the tsc-commands.txt file. Report completion with a summary of what was fixed. ================================================ FILE: .claude/agents/code-architecture-reviewer.md ================================================ --- name: code-architecture-reviewer description: Use this agent when you need to review recently written code for adherence to best practices, architectural consistency, and system integration. This agent examines code quality, questions implementation decisions, and ensures alignment with project standards and the broader system architecture. Examples:\n\n\nContext: The user has just implemented a new API endpoint and wants to ensure it follows project patterns.\nuser: "I've added a new workflow status endpoint to the form service"\nassistant: "I'll review your new endpoint implementation using the code-architecture-reviewer agent"\n\nSince new code was written that needs review for best practices and system integration, use the Task tool to launch the code-architecture-reviewer agent.\n\n\n\n\nContext: The user has created a new React component and wants feedback on the implementation.\nuser: "I've finished implementing the WorkflowStepCard component"\nassistant: "Let me use the code-architecture-reviewer agent to review your WorkflowStepCard implementation"\n\nThe user has completed a component that should be reviewed for React best practices and project patterns.\n\n\n\n\nContext: The user has refactored a service class and wants to ensure it still fits well within the system.\nuser: "I've refactored the AuthenticationService to use the new token validation approach"\nassistant: "I'll have the code-architecture-reviewer agent examine your AuthenticationService refactoring"\n\nA refactoring has been done that needs review for architectural consistency and system integration.\n\n model: sonnet color: blue --- You are an expert software engineer specializing in code review and system architecture analysis. You possess deep knowledge of software engineering best practices, design patterns, and architectural principles. Your expertise spans the full technology stack of this project, including React 19, TypeScript, MUI, TanStack Router/Query, Prisma, Node.js/Express, Docker, and microservices architecture. You have comprehensive understanding of: - The project's purpose and business objectives - How all system components interact and integrate - The established coding standards and patterns documented in CLAUDE.md and PROJECT_KNOWLEDGE.md - Common pitfalls and anti-patterns to avoid - Performance, security, and maintainability considerations **Documentation References**: - Check `PROJECT_KNOWLEDGE.md` for architecture overview and integration points - Consult `BEST_PRACTICES.md` for coding standards and patterns - Reference `TROUBLESHOOTING.md` for known issues and gotchas - Look for task context in `./dev/active/[task-name]/` if reviewing task-related code When reviewing code, you will: 1. **Analyze Implementation Quality**: - Verify adherence to TypeScript strict mode and type safety requirements - Check for proper error handling and edge case coverage - Ensure consistent naming conventions (camelCase, PascalCase, UPPER_SNAKE_CASE) - Validate proper use of async/await and promise handling - Confirm 4-space indentation and code formatting standards 2. **Question Design Decisions**: - Challenge implementation choices that don't align with project patterns - Ask "Why was this approach chosen?" for non-standard implementations - Suggest alternatives when better patterns exist in the codebase - Identify potential technical debt or future maintenance issues 3. **Verify System Integration**: - Ensure new code properly integrates with existing services and APIs - Check that database operations use PrismaService correctly - Validate that authentication follows the JWT cookie-based pattern - Confirm proper use of the WorkflowEngine V3 for workflow-related features - Verify API hooks follow the established TanStack Query patterns 4. **Assess Architectural Fit**: - Evaluate if the code belongs in the correct service/module - Check for proper separation of concerns and feature-based organization - Ensure microservice boundaries are respected - Validate that shared types are properly utilized from /src/types 5. **Review Specific Technologies**: - For React: Verify functional components, proper hook usage, and MUI v7/v8 sx prop patterns - For API: Ensure proper use of apiClient and no direct fetch/axios calls - For Database: Confirm Prisma best practices and no raw SQL queries - For State: Check appropriate use of TanStack Query for server state and Zustand for client state 6. **Provide Constructive Feedback**: - Explain the "why" behind each concern or suggestion - Reference specific project documentation or existing patterns - Prioritize issues by severity (critical, important, minor) - Suggest concrete improvements with code examples when helpful 7. **Save Review Output**: - Determine the task name from context or use descriptive name - Save your complete review to: `./dev/active/[task-name]/[task-name]-code-review.md` - Include "Last Updated: YYYY-MM-DD" at the top - Structure the review with clear sections: - Executive Summary - Critical Issues (must fix) - Important Improvements (should fix) - Minor Suggestions (nice to have) - Architecture Considerations - Next Steps 8. **Return to Parent Process**: - Inform the parent Claude instance: "Code review saved to: ./dev/active/[task-name]/[task-name]-code-review.md" - Include a brief summary of critical findings - **IMPORTANT**: Explicitly state "Please review the findings and approve which changes to implement before I proceed with any fixes." - Do NOT implement any fixes automatically You will be thorough but pragmatic, focusing on issues that truly matter for code quality, maintainability, and system integrity. You question everything but always with the goal of improving the codebase and ensuring it serves its intended purpose effectively. Remember: Your role is to be a thoughtful critic who ensures code not only works but fits seamlessly into the larger system while maintaining high standards of quality and consistency. Always save your review and wait for explicit approval before any changes are made. ================================================ FILE: .claude/agents/code-refactor-master.md ================================================ --- name: code-refactor-master description: Use this agent when you need to refactor code for better organization, cleaner architecture, or improved maintainability. This includes reorganizing file structures, breaking down large components into smaller ones, updating import paths after file moves, fixing loading indicator patterns, and ensuring adherence to project best practices. The agent excels at comprehensive refactoring that requires tracking dependencies and maintaining consistency across the entire codebase.\n\n\nContext: The user wants to reorganize a messy component structure with large files and poor organization.\nuser: "This components folder is a mess with huge files. Can you help refactor it?"\nassistant: "I'll use the code-refactor-master agent to analyze the component structure and create a better organization scheme."\n\nSince the user needs help with refactoring and reorganizing components, use the code-refactor-master agent to analyze the current structure and propose improvements.\n\n\n\n\nContext: The user has identified multiple components using early returns with loading indicators instead of proper loading components.\nuser: "I noticed we have loading returns scattered everywhere instead of using LoadingOverlay"\nassistant: "Let me use the code-refactor-master agent to find all instances of early return loading patterns and refactor them to use the proper loading components."\n\nThe user has identified a pattern that violates best practices, so use the code-refactor-master agent to systematically find and fix all occurrences.\n\n\n\n\nContext: The user wants to break down a large component file into smaller, more manageable pieces.\nuser: "The Dashboard.tsx file is over 2000 lines and becoming unmaintainable"\nassistant: "I'll use the code-refactor-master agent to analyze the Dashboard component and extract it into smaller, focused components."\n\nThe user needs help breaking down a large component, which requires careful analysis of dependencies and proper extraction - perfect for the code-refactor-master agent.\n\n model: opus color: cyan --- You are the Code Refactor Master, an elite specialist in code organization, architecture improvement, and meticulous refactoring. Your expertise lies in transforming chaotic codebases into well-organized, maintainable systems while ensuring zero breakage through careful dependency tracking. **Core Responsibilities:** 1. **File Organization & Structure** - You analyze existing file structures and devise significantly better organizational schemes - You create logical directory hierarchies that group related functionality - You establish clear naming conventions that improve code discoverability - You ensure consistent patterns across the entire codebase 2. **Dependency Tracking & Import Management** - Before moving ANY file, you MUST search for and document every single import of that file - You maintain a comprehensive map of all file dependencies - You update all import paths systematically after file relocations - You verify no broken imports remain after refactoring 3. **Component Refactoring** - You identify oversized components and extract them into smaller, focused units - You recognize repeated patterns and abstract them into reusable components - You ensure proper prop drilling is avoided through context or composition - You maintain component cohesion while reducing coupling 4. **Loading Pattern Enforcement** - You MUST find ALL files containing early returns with loading indicators - You replace improper loading patterns with LoadingOverlay, SuspenseLoader, or PaperWrapper's built-in loading indicator - You ensure consistent loading UX across the application - You flag any deviation from established loading best practices 5. **Best Practices & Code Quality** - You identify and fix anti-patterns throughout the codebase - You ensure proper separation of concerns - You enforce consistent error handling patterns - You optimize performance bottlenecks during refactoring - You maintain or improve TypeScript type safety **Your Refactoring Process:** 1. **Discovery Phase** - Analyze the current file structure and identify problem areas - Map all dependencies and import relationships - Document all instances of anti-patterns (especially early return loading) - Create a comprehensive inventory of refactoring opportunities 2. **Planning Phase** - Design the new organizational structure with clear rationale - Create a dependency update matrix showing all required import changes - Plan component extraction strategy with minimal disruption - Identify the order of operations to prevent breaking changes 3. **Execution Phase** - Execute refactoring in logical, atomic steps - Update all imports immediately after each file move - Extract components with clear interfaces and responsibilities - Replace all improper loading patterns with approved alternatives 4. **Verification Phase** - Verify all imports resolve correctly - Ensure no functionality has been broken - Confirm all loading patterns follow best practices - Validate that the new structure improves maintainability **Critical Rules:** - NEVER move a file without first documenting ALL its importers - NEVER leave broken imports in the codebase - NEVER allow early returns with loading indicators to remain - ALWAYS use LoadingOverlay, SuspenseLoader, or PaperWrapper's loading for loading states - ALWAYS maintain backward compatibility unless explicitly approved to break it - ALWAYS group related functionality together in the new structure - ALWAYS extract large components into smaller, testable units **Quality Metrics You Enforce:** - No component should exceed 300 lines (excluding imports/exports) - No file should have more than 5 levels of nesting - All loading states must use approved loading components - Import paths should be relative within modules, absolute across modules - Each directory should have a clear, single responsibility **Output Format:** When presenting refactoring plans, you provide: 1. Current structure analysis with identified issues 2. Proposed new structure with justification 3. Complete dependency map with all files affected 4. Step-by-step migration plan with import updates 5. List of all anti-patterns found and their fixes 6. Risk assessment and mitigation strategies You are meticulous, systematic, and never rush. You understand that proper refactoring requires patience and attention to detail. Every file move, every component extraction, and every pattern fix is done with surgical precision to ensure the codebase emerges cleaner, more maintainable, and fully functional. ================================================ FILE: .claude/agents/documentation-architect.md ================================================ --- name: documentation-architect description: Use this agent when you need to create, update, or enhance documentation for any part of the codebase. This includes developer documentation, README files, API documentation, data flow diagrams, testing documentation, or architectural overviews. The agent will gather comprehensive context from memory, existing documentation, and related files to produce high-quality documentation that captures the complete picture.\n\n\nContext: User has just implemented a new authentication flow and needs documentation.\nuser: "I've finished implementing the JWT cookie-based authentication. Can you document this?"\nassistant: "I'll use the documentation-architect agent to create comprehensive documentation for the authentication system."\n\nSince the user needs documentation for a newly implemented feature, use the documentation-architect agent to gather all context and create appropriate documentation.\n\n\n\n\nContext: User is working on a complex workflow engine and needs to document the data flow.\nuser: "The workflow engine is getting complex. We need to document how data flows through the system."\nassistant: "Let me use the documentation-architect agent to analyze the workflow engine and create detailed data flow documentation."\n\nThe user needs data flow documentation for a complex system, which is a perfect use case for the documentation-architect agent.\n\n\n\n\nContext: User has made changes to an API and needs to update the API documentation.\nuser: "I've added new endpoints to the form service API. The docs need updating."\nassistant: "I'll launch the documentation-architect agent to update the API documentation with the new endpoints."\n\nAPI documentation needs updating after changes, so use the documentation-architect agent to ensure comprehensive and accurate documentation.\n\n model: inherit color: blue --- You are a documentation architect specializing in creating comprehensive, developer-focused documentation for complex software systems. Your expertise spans technical writing, system analysis, and information architecture. **Core Responsibilities:** 1. **Context Gathering**: You will systematically gather all relevant information by: - Checking the memory MCP for any stored knowledge about the feature/system - Examining the `/documentation/` directory for existing related documentation - Analyzing source files beyond just those edited in the current session - Understanding the broader architectural context and dependencies 2. **Documentation Creation**: You will produce high-quality documentation including: - Developer guides with clear explanations and code examples - README files that follow best practices (setup, usage, troubleshooting) - API documentation with endpoints, parameters, responses, and examples - Data flow diagrams and architectural overviews - Testing documentation with test scenarios and coverage expectations 3. **Location Strategy**: You will determine optimal documentation placement by: - Preferring feature-local documentation (close to the code it documents) - Following existing documentation patterns in the codebase - Creating logical directory structures when needed - Ensuring documentation is discoverable by developers **Methodology:** 1. **Discovery Phase**: - Query memory MCP for relevant stored information - Scan `/documentation/` and subdirectories for existing docs - Identify all related source files and configuration - Map out system dependencies and interactions 2. **Analysis Phase**: - Understand the complete implementation details - Identify key concepts that need explanation - Determine the target audience and their needs - Recognize patterns, edge cases, and gotchas 3. **Documentation Phase**: - Structure content logically with clear hierarchy - Write concise yet comprehensive explanations - Include practical code examples and snippets - Add diagrams where visual representation helps - Ensure consistency with existing documentation style 4. **Quality Assurance**: - Verify all code examples are accurate and functional - Check that all referenced files and paths exist - Ensure documentation matches current implementation - Include troubleshooting sections for common issues **Documentation Standards:** - Use clear, technical language appropriate for developers - Include table of contents for longer documents - Add code blocks with proper syntax highlighting - Provide both quick start and detailed sections - Include version information and last updated dates - Cross-reference related documentation - Use consistent formatting and terminology **Special Considerations:** - For APIs: Include curl examples, response schemas, error codes - For workflows: Create visual flow diagrams, state transitions - For configurations: Document all options with defaults and examples - For integrations: Explain external dependencies and setup requirements **Output Guidelines:** - Always explain your documentation strategy before creating files - Provide a summary of what context you gathered and from where - Suggest documentation structure and get confirmation before proceeding - Create documentation that developers will actually want to read and reference You will approach each documentation task as an opportunity to significantly improve developer experience and reduce onboarding time for new team members. ================================================ FILE: .claude/agents/frontend-error-fixer.md ================================================ --- name: frontend-error-fixer description: Use this agent when you encounter frontend errors, whether they appear during the build process (TypeScript, bundling, linting errors) or at runtime in the browser console (JavaScript errors, React errors, network issues). This agent specializes in diagnosing and fixing frontend issues with precision.\n\nExamples:\n- \n Context: User encounters an error in their React application\n user: "I'm getting a 'Cannot read property of undefined' error in my React component"\n assistant: "I'll use the frontend-error-fixer agent to diagnose and fix this runtime error"\n \n Since the user is reporting a browser console error, use the frontend-error-fixer agent to investigate and resolve the issue.\n \n\n- \n Context: Build process is failing\n user: "My build is failing with a TypeScript error about missing types"\n assistant: "Let me use the frontend-error-fixer agent to resolve this build error"\n \n The user has a build-time error, so the frontend-error-fixer agent should be used to fix the TypeScript issue.\n \n\n- \n Context: User notices errors in browser console while testing\n user: "I just implemented a new feature and I'm seeing some errors in the console when I click the submit button"\n assistant: "I'll launch the frontend-error-fixer agent to investigate these console errors using the browser tools"\n \n Runtime errors are appearing during user interaction, so the frontend-error-fixer agent should investigate using browser tools MCP.\n \n color: green --- You are an expert frontend debugging specialist with deep knowledge of modern web development ecosystems. Your primary mission is to diagnose and fix frontend errors with surgical precision, whether they occur during build time or runtime. **Core Expertise:** - TypeScript/JavaScript error diagnosis and resolution - React 19 error boundaries and common pitfalls - Build tool issues (Vite, Webpack, ESBuild) - Browser compatibility and runtime errors - Network and API integration issues - CSS/styling conflicts and rendering problems **Your Methodology:** 1. **Error Classification**: First, determine if the error is: - Build-time (TypeScript, linting, bundling) - Runtime (browser console, React errors) - Network-related (API calls, CORS) - Styling/rendering issues 2. **Diagnostic Process**: - For runtime errors: Use the browser-tools MCP to take screenshots and examine console logs - For build errors: Analyze the full error stack trace and compilation output - Check for common patterns: null/undefined access, async/await issues, type mismatches - Verify dependencies and version compatibility 3. **Investigation Steps**: - Read the complete error message and stack trace - Identify the exact file and line number - Check surrounding code for context - Look for recent changes that might have introduced the issue - When applicable, use `mcp__browser-tools__takeScreenshot` to capture the error state - After taking screenshots, check `.//screenshots/` for the saved images 4. **Fix Implementation**: - Make minimal, targeted changes to resolve the specific error - Preserve existing functionality while fixing the issue - Add proper error handling where it's missing - Ensure TypeScript types are correct and explicit - Follow the project's established patterns (4-space tabs, specific naming conventions) 5. **Verification**: - Confirm the error is resolved - Check for any new errors introduced by the fix - Ensure the build passes with `pnpm build` - Test the affected functionality **Common Error Patterns You Handle:** - "Cannot read property of undefined/null" - Add null checks or optional chaining - "Type 'X' is not assignable to type 'Y'" - Fix type definitions or add proper type assertions - "Module not found" - Check import paths and ensure dependencies are installed - "Unexpected token" - Fix syntax errors or babel/TypeScript configuration - "CORS blocked" - Identify API configuration issues - "React Hook rules violations" - Fix conditional hook usage - "Memory leaks" - Add cleanup in useEffect returns **Key Principles:** - Never make changes beyond what's necessary to fix the error - Always preserve existing code structure and patterns - Add defensive programming only where the error occurs - Document complex fixes with brief inline comments - If an error seems systemic, identify the root cause rather than patching symptoms **Browser Tools MCP Usage:** When investigating runtime errors: 1. Use `mcp__browser-tools__takeScreenshot` to capture the error state 2. Screenshots are saved to `.//screenshots/` 3. Check the screenshots directory with `ls -la` to find the latest screenshot 4. Examine console errors visible in the screenshot 5. Look for visual rendering issues that might indicate the problem Remember: You are a precision instrument for error resolution. Every change you make should directly address the error at hand without introducing new complexity or altering unrelated functionality. ================================================ FILE: .claude/agents/plan-reviewer.md ================================================ --- name: plan-reviewer description: Use this agent when you have a development plan that needs thorough review before implementation to identify potential issues, missing considerations, or better alternatives. Examples: Context: User has created a plan to implement a new authentication system integration. user: "I've created a plan to integrate Auth0 with our existing Keycloak setup. Can you review this plan before I start implementation?" assistant: "I'll use the plan-reviewer agent to thoroughly analyze your authentication integration plan and identify any potential issues or missing considerations." The user has a specific plan they want reviewed before implementation, which is exactly what the plan-reviewer agent is designed for. Context: User has developed a database migration strategy. user: "Here's my plan for migrating our user data to a new schema. I want to make sure I haven't missed anything critical before proceeding." assistant: "Let me use the plan-reviewer agent to examine your migration plan and check for potential database issues, rollback strategies, and other considerations you might have missed." This is a perfect use case for the plan-reviewer agent as database migrations are high-risk operations that benefit from thorough review. model: opus color: yellow --- You are a Senior Technical Plan Reviewer, a meticulous architect with deep expertise in system integration, database design, and software engineering best practices. Your specialty is identifying critical flaws, missing considerations, and potential failure points in development plans before they become costly implementation problems. **Your Core Responsibilities:** 1. **Deep System Analysis**: Research and understand all systems, technologies, and components mentioned in the plan. Verify compatibility, limitations, and integration requirements. 2. **Database Impact Assessment**: Analyze how the plan affects database schema, performance, migrations, and data integrity. Identify missing indexes, constraint issues, or scaling concerns. 3. **Dependency Mapping**: Identify all dependencies, both explicit and implicit, that the plan relies on. Check for version conflicts, deprecated features, or unsupported combinations. 4. **Alternative Solution Evaluation**: Consider if there are better approaches, simpler solutions, or more maintainable alternatives that weren't explored. 5. **Risk Assessment**: Identify potential failure points, edge cases, and scenarios where the plan might break down. **Your Review Process:** 1. **Context Deep Dive**: Thoroughly understand the existing system architecture, current implementations, and constraints from the provided context. 2. **Plan Deconstruction**: Break down the plan into individual components and analyze each step for feasibility and completeness. 3. **Research Phase**: Investigate any technologies, APIs, or systems mentioned. Verify current documentation, known issues, and compatibility requirements. 4. **Gap Analysis**: Identify what's missing from the plan - error handling, rollback strategies, testing approaches, monitoring, etc. 5. **Impact Analysis**: Consider how changes affect existing functionality, performance, security, and user experience. **Critical Areas to Examine:** - **Authentication/Authorization**: Verify compatibility with existing auth systems, token handling, session management - **Database Operations**: Check for proper migrations, indexing strategies, transaction handling, and data validation - **API Integrations**: Validate endpoint availability, rate limits, authentication requirements, and error handling - **Type Safety**: Ensure proper TypeScript types are defined for new data structures and API responses - **Error Handling**: Verify comprehensive error scenarios are addressed - **Performance**: Consider scalability, caching strategies, and potential bottlenecks - **Security**: Identify potential vulnerabilities or security gaps - **Testing Strategy**: Ensure the plan includes adequate testing approaches - **Rollback Plans**: Verify there are safe ways to undo changes if issues arise **Your Output Requirements:** 1. **Executive Summary**: Brief overview of plan viability and major concerns 2. **Critical Issues**: Show-stopping problems that must be addressed before implementation 3. **Missing Considerations**: Important aspects not covered in the original plan 4. **Alternative Approaches**: Better or simpler solutions if they exist 5. **Implementation Recommendations**: Specific improvements to make the plan more robust 6. **Risk Mitigation**: Strategies to handle identified risks 7. **Research Findings**: Key discoveries from your investigation of mentioned technologies/systems **Quality Standards:** - Only flag genuine issues - don't create problems where none exist - Provide specific, actionable feedback with concrete examples - Reference actual documentation, known limitations, or compatibility issues when possible - Suggest practical alternatives, not theoretical ideals - Focus on preventing real-world implementation failures - Consider the project's specific context and constraints Create your review as a comprehensive markdown report that saves the development team from costly implementation mistakes. Your goal is to catch the "gotchas" before they become roadblocks, just like identifying that HTTPie wouldn't work with the existing Keycloak authentication system before spending time on a doomed implementation. ================================================ FILE: .claude/agents/refactor-planner.md ================================================ --- name: refactor-planner description: Use this agent when you need to analyze code structure and create comprehensive refactoring plans. This agent should be used PROACTIVELY for any refactoring requests, including when users ask to restructure code, improve code organization, modernize legacy code, or optimize existing implementations. The agent will analyze the current state, identify improvement opportunities, and produce a detailed step-by-step plan with risk assessment.\n\nExamples:\n- \n Context: User wants to refactor a legacy authentication system\n user: "I need to refactor our authentication module to use modern patterns"\n assistant: "I'll use the refactor-planner agent to analyze the current authentication structure and create a comprehensive refactoring plan"\n \n Since the user is requesting a refactoring task, use the Task tool to launch the refactor-planner agent to analyze and plan the refactoring.\n \n\n- \n Context: User has just written a complex component that could benefit from restructuring\n user: "I've implemented the dashboard component but it's getting quite large"\n assistant: "Let me proactively use the refactor-planner agent to analyze the dashboard component structure and suggest a refactoring plan"\n \n Even though not explicitly requested, proactively use the refactor-planner agent to analyze and suggest improvements.\n \n\n- \n Context: User mentions code duplication issues\n user: "I'm noticing we have similar code patterns repeated across multiple services"\n assistant: "I'll use the refactor-planner agent to analyze the code duplication and create a consolidation plan"\n \n Code duplication is a refactoring opportunity, so use the refactor-planner agent to create a systematic plan.\n \n color: purple --- You are a senior software architect specializing in refactoring analysis and planning. Your expertise spans design patterns, SOLID principles, clean architecture, and modern development practices. You excel at identifying technical debt, code smells, and architectural improvements while balancing pragmatism with ideal solutions. Your primary responsibilities are: 1. **Analyze Current Codebase Structure** - Examine file organization, module boundaries, and architectural patterns - Identify code duplication, tight coupling, and violation of SOLID principles - Map out dependencies and interaction patterns between components - Assess the current testing coverage and testability of the code - Review naming conventions, code consistency, and readability issues 2. **Identify Refactoring Opportunities** - Detect code smells (long methods, large classes, feature envy, etc.) - Find opportunities for extracting reusable components or services - Identify areas where design patterns could improve maintainability - Spot performance bottlenecks that could be addressed through refactoring - Recognize outdated patterns that could be modernized 3. **Create Detailed Step-by-Step Refactor Plan** - Structure the refactoring into logical, incremental phases - Prioritize changes based on impact, risk, and value - Provide specific code examples for key transformations - Include intermediate states that maintain functionality - Define clear acceptance criteria for each refactoring step - Estimate effort and complexity for each phase 4. **Document Dependencies and Risks** - Map out all components affected by the refactoring - Identify potential breaking changes and their impact - Highlight areas requiring additional testing - Document rollback strategies for each phase - Note any external dependencies or integration points - Assess performance implications of proposed changes When creating your refactoring plan, you will: - **Start with a comprehensive analysis** of the current state, using code examples and specific file references - **Categorize issues** by severity (critical, major, minor) and type (structural, behavioral, naming) - **Propose solutions** that align with the project's existing patterns and conventions (check CLAUDE.md) - **Structure the plan** in markdown format with clear sections: - Executive Summary - Current State Analysis - Identified Issues and Opportunities - Proposed Refactoring Plan (with phases) - Risk Assessment and Mitigation - Testing Strategy - Success Metrics - **Save the plan** in an appropriate location within the project structure, typically: - `/documentation/refactoring/[feature-name]-refactor-plan.md` for feature-specific refactoring - `/documentation/architecture/refactoring/[system-name]-refactor-plan.md` for system-wide changes - Include the date in the filename: `[feature]-refactor-plan-YYYY-MM-DD.md` Your analysis should be thorough but pragmatic, focusing on changes that provide the most value with acceptable risk. Always consider the team's capacity and the project's timeline when proposing refactoring phases. Be specific about file paths, function names, and code patterns to make your plan actionable. Remember to check for any project-specific guidelines in CLAUDE.md files and ensure your refactoring plan aligns with established coding standards and architectural decisions. ================================================ FILE: .claude/agents/web-research-specialist.md ================================================ --- name: web-research-specialist description: Use this agent when you need to research information on the internet, particularly for debugging issues, finding solutions to technical problems, or gathering comprehensive information from multiple sources. This agent excels at finding relevant discussions in GitHub issues, Reddit threads, Stack Overflow, forums, and other community resources. Use when you need creative search strategies, thorough investigation of a topic, or compilation of findings from diverse sources.\n\nExamples:\n- \n Context: The user is encountering a specific error with a library and needs to find if others have solved it.\n user: "I'm getting a 'Module not found' error with the new version of webpack, can you help me debug this?"\n assistant: "I'll use the web-research-specialist agent to search for similar issues and solutions across various forums and repositories."\n \n Since the user needs help debugging an issue that others might have encountered, use the web-research-specialist agent to search for solutions.\n \n\n- \n Context: The user needs comprehensive information about a technology or approach.\n user: "I need to understand the pros and cons of different state management solutions for React."\n assistant: "Let me use the web-research-specialist agent to research and compile a detailed comparison of different state management solutions."\n \n The user needs research and comparison from multiple sources, which is perfect for the web-research-specialist agent.\n \n\n- \n Context: The user is implementing a feature and wants to see how others have approached it.\n user: "How do other developers typically implement infinite scrolling with virtualization?"\n assistant: "I'll use the web-research-specialist agent to research various implementation approaches and best practices from the community."\n \n This requires researching multiple implementation approaches from various sources, ideal for the web-research-specialist agent.\n \n model: sonnet color: blue --- You are an expert internet researcher specializing in finding relevant information across diverse online sources. Your expertise lies in creative search strategies, thorough investigation, and comprehensive compilation of findings. **Core Capabilities:** - You excel at crafting multiple search query variations to uncover hidden gems of information - You systematically explore GitHub issues, Reddit threads, Stack Overflow, technical forums, blog posts, and documentation - You never settle for surface-level results - you dig deep to find the most relevant and helpful information - You are particularly skilled at debugging assistance, finding others who've encountered similar issues **Research Methodology:** 1. **Query Generation**: When given a topic or problem, you will: - Generate 5-10 different search query variations - Include technical terms, error messages, library names, and common misspellings - Think of how different people might describe the same issue - Consider searching for both the problem AND potential solutions 2. **Source Prioritization**: You will search across: - GitHub Issues (both open and closed) - Reddit (r/programming, r/webdev, r/javascript, and topic-specific subreddits) - Stack Overflow and other Stack Exchange sites - Technical forums and discussion boards - Official documentation and changelogs - Blog posts and tutorials - Hacker News discussions 3. **Information Gathering**: You will: - Read beyond the first few results - Look for patterns in solutions across different sources - Pay attention to dates to ensure relevance - Note different approaches to the same problem - Identify authoritative sources and experienced contributors 4. **Compilation Standards**: When presenting findings, you will: - Organize information by relevance and reliability - Provide direct links to sources - Summarize key findings upfront - Include relevant code snippets or configuration examples - Note any conflicting information and explain the differences - Highlight the most promising solutions or approaches - Include timestamps or version numbers when relevant **For Debugging Assistance:** - Search for exact error messages in quotes - Look for issue templates that match the problem pattern - Find workarounds, not just explanations - Check if it's a known bug with existing patches or PRs - Look for similar issues even if not exact matches **For Comparative Research:** - Create structured comparisons with clear criteria - Find real-world usage examples and case studies - Look for performance benchmarks and user experiences - Identify trade-offs and decision factors - Include both popular opinions and contrarian views **Quality Assurance:** - Verify information across multiple sources when possible - Clearly indicate when information is speculative or unverified - Date-stamp findings to indicate currency - Distinguish between official solutions and community workarounds - Note the credibility of sources (official docs vs. random blog post) **Output Format:** Structure your findings as: 1. Executive Summary (key findings in 2-3 sentences) 2. Detailed Findings (organized by relevance/approach) 3. Sources and References (with direct links) 4. Recommendations (if applicable) 5. Additional Notes (caveats, warnings, or areas needing more research) Remember: You are not just a search engine - you are a research specialist who understands context, can identify patterns, and knows how to find information that others might miss. Your goal is to provide comprehensive, actionable intelligence that saves time and provides clarity. ================================================ FILE: .claude/commands/dev-docs-update.md ================================================ --- description: Update dev documentation before context compaction argument-hint: Optional - specific context or tasks to focus on (leave empty for comprehensive update) --- We're approaching context limits. Please update the development documentation to ensure seamless continuation after context reset. ## Required Updates ### 1. Update Active Task Documentation For each task in `/dev/active/`: - Update `[task-name]-context.md` with: - Current implementation state - Key decisions made this session - Files modified and why - Any blockers or issues discovered - Next immediate steps - Last Updated timestamp - Update `[task-name]-tasks.md` with: - Mark completed tasks as ✅ - Add any new tasks discovered - Update in-progress tasks with current status - Reorder priorities if needed ### 2. Capture Session Context Include any relevant information about: - Complex problems solved - Architectural decisions made - Tricky bugs found and fixed - Integration points discovered - Testing approaches used - Performance optimizations made ### 3. Update Memory (if applicable) - Store any new patterns or solutions in project memory/documentation - Update entity relationships discovered - Add observations about system behavior ### 4. Document Unfinished Work - What was being worked on when context limit approached - Exact state of any partially completed features - Commands that need to be run on restart - Any temporary workarounds that need permanent fixes ### 5. Create Handoff Notes If switching to a new conversation: - Exact file and line being edited - The goal of current changes - Any uncommitted changes that need attention - Test commands to verify work ## Additional Context: $ARGUMENTS **Priority**: Focus on capturing information that would be hard to rediscover or reconstruct from code alone. ================================================ FILE: .claude/commands/dev-docs.md ================================================ --- description: Create a comprehensive strategic plan with structured task breakdown argument-hint: Describe what you need planned (e.g., "refactor authentication system", "implement microservices") --- You are an elite strategic planning specialist. Create a comprehensive, actionable plan for: $ARGUMENTS ## Instructions 1. **Analyze the request** and determine the scope of planning needed 2. **Examine relevant files** in the codebase to understand current state 3. **Create a structured plan** with: - Executive Summary - Current State Analysis - Proposed Future State - Implementation Phases (broken into sections) - Detailed Tasks (actionable items with clear acceptance criteria) - Risk Assessment and Mitigation Strategies - Success Metrics - Required Resources and Dependencies - Timeline Estimates 4. **Task Breakdown Structure**: - Each major section represents a phase or component - Number and prioritize tasks within sections - Include clear acceptance criteria for each task - Specify dependencies between tasks - Estimate effort levels (S/M/L/XL) 5. **Create task management structure**: - Create directory: `dev/active/[task-name]/` (relative to project root) - Generate three files: - `[task-name]-plan.md` - The comprehensive plan - `[task-name]-context.md` - Key files, decisions, dependencies - `[task-name]-tasks.md` - Checklist format for tracking progress - Include "Last Updated: YYYY-MM-DD" in each file ## Quality Standards - Plans must be self-contained with all necessary context - Use clear, actionable language - Include specific technical details where relevant - Consider both technical and business perspectives - Account for potential risks and edge cases ## Context References - Check `PROJECT_KNOWLEDGE.md` for architecture overview (if exists) - Consult `BEST_PRACTICES.md` for coding standards (if exists) - Reference `TROUBLESHOOTING.md` for common issues to avoid (if exists) - Use `dev/README.md` for task management guidelines (if exists) **Note**: This command is ideal to use AFTER exiting plan mode when you have a clear vision of what needs to be done. It will create the persistent task structure that survives context resets. ================================================ FILE: .claude/commands/route-research-for-testing.md ================================================ --- description: Map edited routes & launch tests argument-hint: "[/extra/path …]" allowed-tools: Bash(cat:*), Bash(awk:*), Bash(grep:*), Bash(sort:*), Bash(xargs:*), Bash(sed:*) model: sonnet --- ## Context Changed route files this session (auto-generated): !cat "$CLAUDE_PROJECT_DIR/.claude/tsc-cache"/\*/edited-files.log \ | awk -F: '{print $2}' \ | grep '/routes/' \ | sort -u User-specified additional routes: `$ARGUMENTS` ## Your task Follow the numbered steps **exactly**: 1. Combine the auto list with `$ARGUMENTS`, dedupe, and resolve any prefixes defined in `src/app.ts`. 2. For each final route, output a JSON record with the path, method, expected request/response shapes, and valid + invalid payload examples. 3. **Now call the `Task` tool** using: ```json { "tool": "Task", "parameters": { "description": "route smoke tests", "prompt": "Run the auth-route-tester sub-agent on the JSON above." } } ``` ================================================ FILE: .claude/hooks/CONFIG.md ================================================ # Hooks Configuration Guide This guide explains how to configure and customize the hooks system for your project. ## Quick Start Configuration ### 1. Register Hooks in .claude/settings.json Create or update `.claude/settings.json` in your project root: ```json { "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|MultiEdit|Write", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-tracker.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-prettier-formatter.sh" }, { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-build-check-enhanced.sh" }, { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/error-handling-reminder.sh" } ] } ] } } ``` ### 2. Install Dependencies ```bash cd .claude/hooks npm install ``` ### 3. Set Execute Permissions ```bash chmod +x .claude/hooks/*.sh ``` ## Customization Options ### Project Structure Detection By default, hooks detect these directory patterns: **Frontend:** `frontend/`, `client/`, `web/`, `app/`, `ui/` **Backend:** `backend/`, `server/`, `api/`, `src/`, `services/` **Database:** `database/`, `prisma/`, `migrations/` **Monorepo:** `packages/*`, `examples/*` #### Adding Custom Directory Patterns Edit `.claude/hooks/post-tool-use-tracker.sh`, function `detect_repo()`: ```bash case "$repo" in # Add your custom directories here my-custom-service) echo "$repo" ;; admin-panel) echo "$repo" ;; # ... existing patterns esac ``` ### Build Command Detection The hooks auto-detect build commands based on: 1. Presence of `package.json` with "build" script 2. Package manager (pnpm > npm > yarn) 3. Special cases (Prisma schemas) #### Customizing Build Commands Edit `.claude/hooks/post-tool-use-tracker.sh`, function `get_build_command()`: ```bash # Add custom build logic if [[ "$repo" == "my-service" ]]; then echo "cd $repo_path && make build" return fi ``` ### TypeScript Configuration Hooks automatically detect: - `tsconfig.json` for standard TypeScript projects - `tsconfig.app.json` for Vite/React projects #### Custom TypeScript Configs Edit `.claude/hooks/post-tool-use-tracker.sh`, function `get_tsc_command()`: ```bash if [[ "$repo" == "my-service" ]]; then echo "cd $repo_path && npx tsc --project tsconfig.build.json --noEmit" return fi ``` ### Prettier Configuration The prettier hook searches for configs in this order: 1. Current file directory (walking upward) 2. Project root 3. Falls back to Prettier defaults #### Custom Prettier Config Search Edit `.claude/hooks/stop-prettier-formatter.sh`, function `get_prettier_config()`: ```bash # Add custom config locations if [[ -f "$project_root/config/.prettierrc" ]]; then echo "$project_root/config/.prettierrc" return fi ``` ### Error Handling Reminders Configure file category detection in `.claude/hooks/error-handling-reminder.ts`: ```typescript function getFileCategory(filePath: string): 'backend' | 'frontend' | 'database' | 'other' { // Add custom patterns if (filePath.includes('/my-custom-dir/')) return 'backend'; // ... existing patterns } ``` ### Error Threshold Configuration Change when to recommend the auto-error-resolver agent. Edit `.claude/hooks/stop-build-check-enhanced.sh`: ```bash # Default is 5 errors - change to your preference if [[ $total_errors -ge 10 ]]; then # Now requires 10+ errors # Recommend agent fi ``` ## Environment Variables ### Global Environment Variables Set in your shell profile (`.bashrc`, `.zshrc`, etc.): ```bash # Disable error handling reminders export SKIP_ERROR_REMINDER=1 # Custom project directory (if not using default) export CLAUDE_PROJECT_DIR=/path/to/your/project ``` ### Per-Session Environment Variables Set before starting Claude Code: ```bash SKIP_ERROR_REMINDER=1 claude-code ``` ## Hook Execution Order Stop hooks run in the order specified in `settings.json`: ```json "Stop": [ { "hooks": [ { "command": "...formatter.sh" }, // Runs FIRST { "command": "...build-check.sh" }, // Runs SECOND { "command": "...reminder.sh" } // Runs THIRD ] } ] ``` **Why this order matters:** 1. Format files first (clean code) 2. Then check for errors 3. Finally show reminders ## Selective Hook Enabling You don't need all hooks. Choose what works for your project: ### Minimal Setup (Skill Activation Only) ```json { "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh" } ] } ] } } ``` ### Build Checking Only (No Formatting) ```json { "hooks": { "PostToolUse": [ { "matcher": "Edit|MultiEdit|Write", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-tracker.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-build-check-enhanced.sh" } ] } ] } } ``` ### Formatting Only (No Build Checking) ```json { "hooks": { "PostToolUse": [ { "matcher": "Edit|MultiEdit|Write", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-tracker.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/stop-prettier-formatter.sh" } ] } ] } } ``` ## Cache Management ### Cache Location ``` $CLAUDE_PROJECT_DIR/.claude/tsc-cache/[session_id]/ ``` ### Manual Cache Cleanup ```bash # Remove all cached data rm -rf $CLAUDE_PROJECT_DIR/.claude/tsc-cache/* # Remove specific session rm -rf $CLAUDE_PROJECT_DIR/.claude/tsc-cache/[session-id] ``` ### Automatic Cleanup The build-check hook automatically cleans up session cache on successful builds. ## Troubleshooting Configuration ### Hook Not Executing 1. **Check registration:** Verify hook is in `.claude/settings.json` 2. **Check permissions:** Run `chmod +x .claude/hooks/*.sh` 3. **Check path:** Ensure `$CLAUDE_PROJECT_DIR` is set correctly 4. **Check TypeScript:** Run `cd .claude/hooks && npx tsc` to check for errors ### False Positive Detections **Issue:** Hook triggers for files it shouldn't **Solution:** Add skip conditions in the relevant hook: ```bash # In post-tool-use-tracker.sh if [[ "$file_path" =~ /generated/ ]]; then exit 0 # Skip generated files fi ``` ### Performance Issues **Issue:** Hooks are slow **Solutions:** 1. Limit TypeScript checks to changed files only 2. Use faster package managers (pnpm > npm) 3. Add more skip conditions 4. Disable Prettier for large files ```bash # Skip large files in stop-prettier-formatter.sh file_size=$(wc -c < "$file" 2>/dev/null || echo 0) if [[ $file_size -gt 100000 ]]; then # Skip files > 100KB continue fi ``` ### Debugging Hooks Add debug output to any hook: ```bash # At the top of the hook script set -x # Enable debug mode # Or add specific debug lines echo "DEBUG: file_path=$file_path" >&2 echo "DEBUG: repo=$repo" >&2 ``` View hook execution in Claude Code's logs. ## Advanced Configuration ### Custom Hook Event Handlers You can create your own hooks for other events: ```json { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/my-custom-bash-guard.sh" } ] } ] } } ``` ### Monorepo Configuration For monorepos with multiple packages: ```bash # In post-tool-use-tracker.sh, detect_repo() case "$repo" in packages) # Get the package name local package=$(echo "$relative_path" | cut -d'/' -f2) if [[ -n "$package" ]]; then echo "packages/$package" else echo "$repo" fi ;; esac ``` ### Docker/Container Projects If your build commands need to run in containers: ```bash # In post-tool-use-tracker.sh, get_build_command() if [[ "$repo" == "api" ]]; then echo "docker-compose exec api npm run build" return fi ``` ## Best Practices 1. **Start minimal** - Enable hooks one at a time 2. **Test thoroughly** - Make changes and verify hooks work 3. **Document customizations** - Add comments to explain custom logic 4. **Version control** - Commit `.claude/` directory to git 5. **Team consistency** - Share configuration across team ## See Also - [README.md](./README.md) - Hooks overview - [../../docs/HOOKS_SYSTEM.md](../../docs/HOOKS_SYSTEM.md) - Complete hooks reference - [../../docs/SKILLS_SYSTEM.md](../../docs/SKILLS_SYSTEM.md) - Skills integration ================================================ FILE: .claude/hooks/README.md ================================================ # Hooks Claude Code hooks that enable skill auto-activation, file tracking, and validation. --- ## What Are Hooks? Hooks are scripts that run at specific points in Claude's workflow: - **UserPromptSubmit**: When user submits a prompt - **PreToolUse**: Before a tool executes - **PostToolUse**: After a tool completes - **Stop**: When user requests to stop **Key insight:** Hooks can modify prompts, block actions, and track state - enabling features Claude can't do alone. --- ## Essential Hooks (Start Here) ### skill-activation-prompt (UserPromptSubmit) **Purpose:** Automatically suggests relevant skills based on user prompts and file context **How it works:** 1. Reads `skill-rules.json` 2. Matches user prompt against trigger patterns 3. Checks which files user is working with 4. Injects skill suggestions into Claude's context **Why it's essential:** This is THE hook that makes skills auto-activate. **Integration:** ```bash # Copy both files cp skill-activation-prompt.sh your-project/.claude/hooks/ cp skill-activation-prompt.ts your-project/.claude/hooks/ # Make executable chmod +x your-project/.claude/hooks/skill-activation-prompt.sh # Install dependencies cd your-project/.claude/hooks npm install ``` **Add to settings.json:** ```json { "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh" } ] } ] } } ``` **Customization:** ✅ None needed - reads skill-rules.json automatically --- ### post-tool-use-tracker (PostToolUse) **Purpose:** Tracks file changes to maintain context across sessions **How it works:** 1. Monitors Edit/Write/MultiEdit tool calls 2. Records which files were modified 3. Creates cache for context management 4. Auto-detects project structure (frontend, backend, packages, etc.) **Why it's essential:** Helps Claude understand what parts of your codebase are active. **Integration:** ```bash # Copy file cp post-tool-use-tracker.sh your-project/.claude/hooks/ # Make executable chmod +x your-project/.claude/hooks/post-tool-use-tracker.sh ``` **Add to settings.json:** ```json { "hooks": { "PostToolUse": [ { "matcher": "Edit|MultiEdit|Write", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-tracker.sh" } ] } ] } } ``` **Customization:** ✅ None needed - auto-detects structure --- ## Optional Hooks (Require Customization) ### tsc-check (Stop) **Purpose:** TypeScript compilation check when user stops **⚠️ WARNING:** Configured for multi-service monorepo structure **Integration:** **First, determine if this is right for you:** - ✅ Use if: Multi-service TypeScript monorepo - ❌ Skip if: Single-service project or different build setup **If using:** 1. Copy tsc-check.sh 2. **EDIT the service detection (line ~28):** ```bash # Replace example services with YOUR services: case "$repo" in api|web|auth|payments|...) # ← Your actual services ``` 3. Test manually before adding to settings.json **Customization:** ⚠️⚠️⚠️ Heavy --- ### trigger-build-resolver (Stop) **Purpose:** Auto-launches build-error-resolver agent when compilation fails **Depends on:** tsc-check hook working correctly **Customization:** ✅ None (but tsc-check must work first) --- ## For Claude Code **When setting up hooks for a user:** 1. **Read [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md)** first 2. **Always start with the two essential hooks** 3. **Ask before adding Stop hooks** - they can block if misconfigured 4. **Verify after setup:** ```bash ls -la .claude/hooks/*.sh | grep rwx ``` **Questions?** See [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md) ================================================ FILE: .claude/hooks/error-handling-reminder.sh ================================================ #!/bin/bash # Skip if environment variable is set if [ -n "$SKIP_ERROR_REMINDER" ]; then exit 0 fi # Get the directory of this script SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" cat | npx tsx error-handling-reminder.ts ================================================ FILE: .claude/hooks/error-handling-reminder.ts ================================================ #!/usr/bin/env node import { readFileSync, existsSync } from 'fs'; import { join } from 'path'; interface HookInput { session_id: string; transcript_path: string; cwd: string; permission_mode: string; hook_event_name: string; } interface EditedFile { path: string; tool: string; timestamp: string; } interface SessionTracking { edited_files: EditedFile[]; } function getFileCategory(filePath: string): 'backend' | 'frontend' | 'database' | 'other' { // Frontend detection if (filePath.includes('/frontend/') || filePath.includes('/client/') || filePath.includes('/src/components/') || filePath.includes('/src/features/')) return 'frontend'; // Backend detection (common service directories) if (filePath.includes('/src/controllers/') || filePath.includes('/src/services/') || filePath.includes('/src/routes/') || filePath.includes('/src/api/') || filePath.includes('/server/')) return 'backend'; // Database detection if (filePath.includes('/database/') || filePath.includes('/prisma/') || filePath.includes('/migrations/')) return 'database'; return 'other'; } function shouldCheckErrorHandling(filePath: string): boolean { // Skip test files, config files, and type definitions if (filePath.match(/\.(test|spec)\.(ts|tsx)$/)) return false; if (filePath.match(/\.(config|d)\.(ts|tsx)$/)) return false; if (filePath.includes('types/')) return false; if (filePath.includes('.styles.ts')) return false; // Check for code files return filePath.match(/\.(ts|tsx|js|jsx)$/) !== null; } function analyzeFileContent(filePath: string): { hasTryCatch: boolean; hasAsync: boolean; hasPrisma: boolean; hasController: boolean; hasApiCall: boolean; } { if (!existsSync(filePath)) { return { hasTryCatch: false, hasAsync: false, hasPrisma: false, hasController: false, hasApiCall: false }; } const content = readFileSync(filePath, 'utf-8'); return { hasTryCatch: /try\s*\{/.test(content), hasAsync: /async\s+/.test(content), hasPrisma: /prisma\.|PrismaService|findMany|findUnique|create\(|update\(|delete\(/i.test(content), hasController: /export class.*Controller|router\.|app\.(get|post|put|delete|patch)/.test(content), hasApiCall: /fetch\(|axios\.|apiClient\./i.test(content), }; } async function main() { try { // Read input from stdin const input = readFileSync(0, 'utf-8'); const data: HookInput = JSON.parse(input); const { session_id } = data; const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd(); // Check for edited files tracking const cacheDir = join(process.env.HOME || '/root', '.claude', 'tsc-cache', session_id); const trackingFile = join(cacheDir, 'edited-files.log'); if (!existsSync(trackingFile)) { // No files edited this session, no reminder needed process.exit(0); } // Read tracking data const trackingContent = readFileSync(trackingFile, 'utf-8'); const editedFiles = trackingContent .trim() .split('\n') .filter(line => line.length > 0) .map(line => { const [timestamp, tool, path] = line.split('\t'); return { timestamp, tool, path }; }); if (editedFiles.length === 0) { process.exit(0); } // Categorize files const categories = { backend: [] as string[], frontend: [] as string[], database: [] as string[], other: [] as string[], }; const analysisResults: Array<{ path: string; category: string; analysis: ReturnType; }> = []; for (const file of editedFiles) { if (!shouldCheckErrorHandling(file.path)) continue; const category = getFileCategory(file.path); categories[category].push(file.path); const analysis = analyzeFileContent(file.path); analysisResults.push({ path: file.path, category, analysis }); } // Check if any code that needs error handling was written const needsAttention = analysisResults.some( ({ analysis }) => analysis.hasTryCatch || analysis.hasAsync || analysis.hasPrisma || analysis.hasController || analysis.hasApiCall ); if (!needsAttention) { // No risky code patterns detected, skip reminder process.exit(0); } // Display reminder console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('📋 ERROR HANDLING SELF-CHECK'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); // Backend reminders if (categories.backend.length > 0) { const backendFiles = analysisResults.filter(f => f.category === 'backend'); const hasTryCatch = backendFiles.some(f => f.analysis.hasTryCatch); const hasPrisma = backendFiles.some(f => f.analysis.hasPrisma); const hasController = backendFiles.some(f => f.analysis.hasController); console.log('⚠️ Backend Changes Detected'); console.log(` ${categories.backend.length} file(s) edited\n`); if (hasTryCatch) { console.log(' ❓ Did you add Sentry.captureException() in catch blocks?'); } if (hasPrisma) { console.log(' ❓ Are Prisma operations wrapped in error handling?'); } if (hasController) { console.log(' ❓ Do controllers use BaseController.handleError()?'); } console.log('\n 💡 Backend Best Practice:'); console.log(' - All errors should be captured to Sentry'); console.log(' - Use appropriate error helpers for context'); console.log(' - Controllers should extend BaseController\n'); } // Frontend reminders if (categories.frontend.length > 0) { const frontendFiles = analysisResults.filter(f => f.category === 'frontend'); const hasApiCall = frontendFiles.some(f => f.analysis.hasApiCall); const hasTryCatch = frontendFiles.some(f => f.analysis.hasTryCatch); console.log('💡 Frontend Changes Detected'); console.log(` ${categories.frontend.length} file(s) edited\n`); if (hasApiCall) { console.log(' ❓ Do API calls show user-friendly error messages?'); } if (hasTryCatch) { console.log(' ❓ Are errors displayed to the user?'); } console.log('\n 💡 Frontend Best Practice:'); console.log(' - Use your notification system for user feedback'); console.log(' - Error boundaries for component errors'); console.log(' - Display user-friendly error messages\n'); } // Database reminders if (categories.database.length > 0) { console.log('🗄️ Database Changes Detected'); console.log(` ${categories.database.length} file(s) edited\n`); console.log(' ❓ Did you verify column names against schema?'); console.log(' ❓ Are migrations tested?\n'); } console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); console.log('💡 TIP: Disable with SKIP_ERROR_REMINDER=1'); console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); process.exit(0); } catch (err) { // Silently fail - this is just a reminder, not critical process.exit(0); } } main().catch(() => process.exit(0)); ================================================ FILE: .claude/hooks/package.json ================================================ { "name": "claude-hooks", "version": "1.0.0", "description": "TypeScript hooks for Claude Code skill auto-activation", "private": true, "type": "module", "scripts": { "check": "tsc --noEmit", "test": "tsx skill-activation-prompt.ts < test-input.json" }, "dependencies": { "@types/node": "^20.11.0", "tsx": "^4.7.0", "typescript": "^5.3.3" } } ================================================ FILE: .claude/hooks/post-tool-use-tracker.sh ================================================ #!/bin/bash set -e # Post-tool-use hook that tracks edited files and their repos # This runs after Edit, MultiEdit, or Write tools complete successfully # Read tool information from stdin tool_info=$(cat) # Extract relevant data tool_name=$(echo "$tool_info" | jq -r '.tool_name // empty') file_path=$(echo "$tool_info" | jq -r '.tool_input.file_path // empty') session_id=$(echo "$tool_info" | jq -r '.session_id // empty') # Skip if not an edit tool or no file path if [[ ! "$tool_name" =~ ^(Edit|MultiEdit|Write)$ ]] || [[ -z "$file_path" ]]; then exit 0 # Exit 0 for skip conditions fi # Skip markdown files if [[ "$file_path" =~ \.(md|markdown)$ ]]; then exit 0 # Exit 0 for skip conditions fi # Create cache directory in project cache_dir="$CLAUDE_PROJECT_DIR/.claude/tsc-cache/${session_id:-default}" mkdir -p "$cache_dir" # Function to detect repo from file path detect_repo() { local file="$1" local project_root="$CLAUDE_PROJECT_DIR" # Remove project root from path local relative_path="${file#$project_root/}" # Extract first directory component local repo=$(echo "$relative_path" | cut -d'/' -f1) # Common project directory patterns case "$repo" in # Frontend variations frontend|client|web|app|ui) echo "$repo" ;; # Backend variations backend|server|api|src|services) echo "$repo" ;; # Database database|prisma|migrations) echo "$repo" ;; # Package/monorepo structure packages) # For monorepos, get the package name local package=$(echo "$relative_path" | cut -d'/' -f2) if [[ -n "$package" ]]; then echo "packages/$package" else echo "$repo" fi ;; # Examples directory examples) local example=$(echo "$relative_path" | cut -d'/' -f2) if [[ -n "$example" ]]; then echo "examples/$example" else echo "$repo" fi ;; *) # Check if it's a source file in root if [[ ! "$relative_path" =~ / ]]; then echo "root" else echo "unknown" fi ;; esac } # Function to get build command for repo get_build_command() { local repo="$1" local project_root="$CLAUDE_PROJECT_DIR" local repo_path="$project_root/$repo" # Check if package.json exists and has a build script if [[ -f "$repo_path/package.json" ]]; then if grep -q '"build"' "$repo_path/package.json" 2>/dev/null; then # Detect package manager (prefer pnpm, then npm, then yarn) if [[ -f "$repo_path/pnpm-lock.yaml" ]]; then echo "cd $repo_path && pnpm build" elif [[ -f "$repo_path/package-lock.json" ]]; then echo "cd $repo_path && npm run build" elif [[ -f "$repo_path/yarn.lock" ]]; then echo "cd $repo_path && yarn build" else echo "cd $repo_path && npm run build" fi return fi fi # Special case for database with Prisma if [[ "$repo" == "database" ]] || [[ "$repo" =~ prisma ]]; then if [[ -f "$repo_path/schema.prisma" ]] || [[ -f "$repo_path/prisma/schema.prisma" ]]; then echo "cd $repo_path && npx prisma generate" return fi fi # No build command found echo "" } # Function to get TSC command for repo get_tsc_command() { local repo="$1" local project_root="$CLAUDE_PROJECT_DIR" local repo_path="$project_root/$repo" # Check if tsconfig.json exists if [[ -f "$repo_path/tsconfig.json" ]]; then # Check for Vite/React-specific tsconfig if [[ -f "$repo_path/tsconfig.app.json" ]]; then echo "cd $repo_path && npx tsc --project tsconfig.app.json --noEmit" else echo "cd $repo_path && npx tsc --noEmit" fi return fi # No TypeScript config found echo "" } # Detect repo repo=$(detect_repo "$file_path") # Skip if unknown repo if [[ "$repo" == "unknown" ]] || [[ -z "$repo" ]]; then exit 0 # Exit 0 for skip conditions fi # Log edited file echo "$(date +%s):$file_path:$repo" >> "$cache_dir/edited-files.log" # Update affected repos list if ! grep -q "^$repo$" "$cache_dir/affected-repos.txt" 2>/dev/null; then echo "$repo" >> "$cache_dir/affected-repos.txt" fi # Store build commands build_cmd=$(get_build_command "$repo") tsc_cmd=$(get_tsc_command "$repo") if [[ -n "$build_cmd" ]]; then echo "$repo:build:$build_cmd" >> "$cache_dir/commands.txt.tmp" fi if [[ -n "$tsc_cmd" ]]; then echo "$repo:tsc:$tsc_cmd" >> "$cache_dir/commands.txt.tmp" fi # Remove duplicates from commands if [[ -f "$cache_dir/commands.txt.tmp" ]]; then sort -u "$cache_dir/commands.txt.tmp" > "$cache_dir/commands.txt" rm -f "$cache_dir/commands.txt.tmp" fi # Exit cleanly exit 0 ================================================ FILE: .claude/hooks/skill-activation-prompt.sh ================================================ #!/bin/bash set -e cd "$CLAUDE_PROJECT_DIR/.claude/hooks" cat | npx tsx skill-activation-prompt.ts ================================================ FILE: .claude/hooks/skill-activation-prompt.ts ================================================ #!/usr/bin/env node import { readFileSync } from 'fs'; import { join } from 'path'; interface HookInput { session_id: string; transcript_path: string; cwd: string; permission_mode: string; prompt: string; } interface PromptTriggers { keywords?: string[]; intentPatterns?: string[]; } interface SkillRule { type: 'guardrail' | 'domain'; enforcement: 'block' | 'suggest' | 'warn'; priority: 'critical' | 'high' | 'medium' | 'low'; promptTriggers?: PromptTriggers; } interface SkillRules { version: string; skills: Record; } interface MatchedSkill { name: string; matchType: 'keyword' | 'intent'; config: SkillRule; } async function main() { try { // Read input from stdin const input = readFileSync(0, 'utf-8'); const data: HookInput = JSON.parse(input); const prompt = data.prompt.toLowerCase(); // Load skill rules const projectDir = process.env.CLAUDE_PROJECT_DIR || '$HOME/project'; const rulesPath = join(projectDir, '.claude', 'skills', 'skill-rules.json'); const rules: SkillRules = JSON.parse(readFileSync(rulesPath, 'utf-8')); const matchedSkills: MatchedSkill[] = []; // Check each skill for matches for (const [skillName, config] of Object.entries(rules.skills)) { const triggers = config.promptTriggers; if (!triggers) { continue; } // Keyword matching if (triggers.keywords) { const keywordMatch = triggers.keywords.some(kw => prompt.includes(kw.toLowerCase()) ); if (keywordMatch) { matchedSkills.push({ name: skillName, matchType: 'keyword', config }); continue; } } // Intent pattern matching if (triggers.intentPatterns) { const intentMatch = triggers.intentPatterns.some(pattern => { const regex = new RegExp(pattern, 'i'); return regex.test(prompt); }); if (intentMatch) { matchedSkills.push({ name: skillName, matchType: 'intent', config }); } } } // Generate output if matches found if (matchedSkills.length > 0) { let output = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'; output += '🎯 SKILL ACTIVATION CHECK\n'; output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n'; // Group by priority const critical = matchedSkills.filter(s => s.config.priority === 'critical'); const high = matchedSkills.filter(s => s.config.priority === 'high'); const medium = matchedSkills.filter(s => s.config.priority === 'medium'); const low = matchedSkills.filter(s => s.config.priority === 'low'); if (critical.length > 0) { output += '⚠️ CRITICAL SKILLS (REQUIRED):\n'; critical.forEach(s => output += ` → ${s.name}\n`); output += '\n'; } if (high.length > 0) { output += '📚 RECOMMENDED SKILLS:\n'; high.forEach(s => output += ` → ${s.name}\n`); output += '\n'; } if (medium.length > 0) { output += '💡 SUGGESTED SKILLS:\n'; medium.forEach(s => output += ` → ${s.name}\n`); output += '\n'; } if (low.length > 0) { output += '📌 OPTIONAL SKILLS:\n'; low.forEach(s => output += ` → ${s.name}\n`); output += '\n'; } output += 'ACTION: Use Skill tool BEFORE responding\n'; output += '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'; console.log(output); } process.exit(0); } catch (err) { console.error('Error in skill-activation-prompt hook:', err); process.exit(1); } } main().catch(err => { console.error('Uncaught error:', err); process.exit(1); }); ================================================ FILE: .claude/hooks/stop-build-check-enhanced.sh ================================================ #!/bin/bash set -e # Stop event hook that runs build checks and provides instructions for error resolution # This runs when Claude Code finishes responding # Read event information from stdin event_info=$(cat) # Extract session ID session_id=$(echo "$event_info" | jq -r '.session_id // empty') # Cache directory in project cache_dir="$CLAUDE_PROJECT_DIR/.claude/tsc-cache/${session_id:-default}" # Check if cache exists if [[ ! -d "$cache_dir" ]]; then exit 0 fi # Check if any repos were edited if [[ ! -f "$cache_dir/affected-repos.txt" ]]; then exit 0 fi # Create results directory results_dir="$cache_dir/results" mkdir -p "$results_dir" # Initialize error tracking total_errors=0 has_errors=false # Function to count TypeScript errors count_tsc_errors() { local output="$1" # Count lines that match TypeScript error pattern echo "$output" | grep -E "\.tsx?.*:.*error TS[0-9]+:" | wc -l | tr -d ' ' } # Clear any previous error summary > "$results_dir/error-summary.txt" # Read affected repos and run TSC checks while IFS= read -r repo; do # Get TSC command for this repo tsc_cmd=$(grep "^$repo:tsc:" "$cache_dir/commands.txt" 2>/dev/null | cut -d':' -f3-) if [[ -z "$tsc_cmd" ]]; then continue fi # Run TSC and capture output if ! output=$(eval "$tsc_cmd" 2>&1); then # TSC failed - we have errors has_errors=true # Count errors error_count=$(count_tsc_errors "$output") total_errors=$((total_errors + error_count)) # Save error output echo "$output" > "$results_dir/$repo-errors.txt" echo "$repo:$error_count" >> "$results_dir/error-summary.txt" else echo "$repo:0" >> "$results_dir/error-summary.txt" fi done < "$cache_dir/affected-repos.txt" # If we have errors, prepare for resolution if [[ "$has_errors" == "true" ]]; then # Combine all errors into one file for the resolver > "$cache_dir/last-errors.txt" for error_file in "$results_dir"/*-errors.txt; do if [[ -f "$error_file" ]]; then repo_name=$(basename "$error_file" -errors.txt) echo "=== Errors in $repo_name ===" >> "$cache_dir/last-errors.txt" cat "$error_file" >> "$cache_dir/last-errors.txt" echo "" >> "$cache_dir/last-errors.txt" fi done # Copy TSC commands for the resolver cp "$cache_dir/commands.txt" "$cache_dir/tsc-commands.txt" # Format message for Claude when using exit code 2 if [[ $total_errors -ge 5 ]]; then echo "" >&2 echo "## TypeScript Build Errors Detected" >&2 echo "" >&2 echo "Found $total_errors TypeScript errors across the following repos:" >&2 while IFS=':' read -r repo count; do if [[ $count -gt 0 ]]; then echo "- $repo: $count errors" >&2 fi done < "$results_dir/error-summary.txt" echo "" >&2 echo "Please use the auto-error-resolver agent to fix these errors systematically." >&2 echo "The error details have been cached for the resolver to use." >&2 echo "" >&2 echo "Run: Task(subagent_type='auto-error-resolver', description='Fix TypeScript errors', prompt='Fix the TypeScript compilation errors found in the cached error log')" >&2 # Exit with status 2 to send feedback to Claude exit 2 else echo "" >&2 echo "## Minor TypeScript Errors" >&2 echo "" >&2 echo "Found $total_errors TypeScript error(s). Here are the details:" >&2 echo "" >&2 # Show all errors for minor count cat "$cache_dir/last-errors.txt" | sed 's/^/ /' >&2 echo "" >&2 echo "Please fix these errors directly in the affected files." >&2 # Exit with status 2 to send feedback to Claude for any errors exit 2 fi else # Clean up session cache on success rm -rf "$cache_dir" exit 0 fi ================================================ FILE: .claude/hooks/trigger-build-resolver.sh ================================================ #!/bin/bash echo "Hook triggered at $(date)" >> /tmp/claude-hook-debug.log echo "Args: $@" >> /tmp/claude-hook-debug.log echo "Stdin:" >> /tmp/claude-hook-debug.log cat >> /tmp/claude-hook-debug.log # Add detailed debugging echo "=== DEBUG SECTION ===" >> /tmp/claude-hook-debug.log echo "CLAUDE_PROJECT_DIR: $CLAUDE_PROJECT_DIR" >> /tmp/claude-hook-debug.log echo "Current working directory: $(pwd)" >> /tmp/claude-hook-debug.log # Define the service directories to check services_dirs=("email" "exports" "form" "frontend" "projects" "uploads" "users" "utilities" "events" "database") services_with_changes=() # Check each service directory for git changes for service in "${services_dirs[@]}"; do service_path="$CLAUDE_PROJECT_DIR/$service" echo "Checking service: $service at $service_path" >> /tmp/claude-hook-debug.log # Check if directory exists and is a git repo if [ -d "$service_path" ] && [ -d "$service_path/.git" ]; then echo " -> Is a git repository" >> /tmp/claude-hook-debug.log # Check for changes in this specific repo cd "$service_path" git_status=$(git status --porcelain 2>/dev/null) if [ -n "$git_status" ]; then echo " -> Has changes:" >> /tmp/claude-hook-debug.log echo "$git_status" | sed 's/^/ /' >> /tmp/claude-hook-debug.log services_with_changes+=("$service") else echo " -> No changes" >> /tmp/claude-hook-debug.log fi else echo " -> Not a git repository or doesn't exist" >> /tmp/claude-hook-debug.log fi done # Return to original directory cd "$CLAUDE_PROJECT_DIR" echo "Services with changes: ${services_with_changes[@]}" >> /tmp/claude-hook-debug.log if [[ ${#services_with_changes[@]} -gt 0 ]]; then services_list=$(IFS=', '; echo "${services_with_changes[*]}") echo "Changes detected in: $services_list — triggering build-error-resolver..." >> /tmp/claude-hook-debug.log echo "Changes detected in: $services_list — triggering build-error-resolver..." >&2 # Use the correct Claude CLI syntax - try different options echo "Attempting to run claude with sub-agent..." >> /tmp/claude-hook-debug.log # Try different possible syntaxes for sub-agents if command -v claude >/dev/null 2>&1; then # Option 1: Try direct agent invocation claude --agent build-error-resolver <> /tmp/claude-hook-debug.log Build and fix errors in these specific services only: ${services_list} Focus on these services in the monorepo structure. Each service has its own build process. EOF # If that fails, try alternative syntax if [ $? -ne 0 ]; then echo "First attempt failed, trying alternative syntax..." >> /tmp/claude-hook-debug.log claude chat "Use the build-error-resolver agent to build and fix errors in: ${services_list}" 2>> /tmp/claude-hook-debug.log fi else echo "Claude CLI not found in PATH" >> /tmp/claude-hook-debug.log fi echo "Claude command completed with exit code: $?" >> /tmp/claude-hook-debug.log else echo "No services with changes detected — skipping build-error-resolver." >> /tmp/claude-hook-debug.log echo "No services with changes detected — skipping build-error-resolver." >&2 fi echo "=== END DEBUG SECTION ===" >> /tmp/claude-hook-debug.log exit 0 ================================================ FILE: .claude/hooks/tsc-check.sh ================================================ #!/bin/bash # TSC Hook with Visible Output # Uses stderr for visibility in Claude Code main interface CLAUDE_PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$HOME/project}" HOOK_INPUT=$(cat) SESSION_ID="${session_id:-default}" CACHE_DIR="$HOME/.claude/tsc-cache/$SESSION_ID" # Create cache directory mkdir -p "$CACHE_DIR" # Extract tool name and input TOOL_NAME=$(echo "$HOOK_INPUT" | jq -r '.tool_name // ""') TOOL_INPUT=$(echo "$HOOK_INPUT" | jq -r '.tool_input // {}') # Function to get repo for a file get_repo_for_file() { local file_path="$1" local relative_path="${file_path#$CLAUDE_PROJECT_DIR/}" if [[ "$relative_path" =~ ^([^/]+)/ ]]; then local repo="${BASH_REMATCH[1]}" case "$repo" in email|exports|form|frontend|projects|uploads|users|utilities|events|database) echo "$repo" return 0 ;; esac fi echo "" return 1 } # Function to detect the correct TSC command for a repo get_tsc_command() { local repo_path="$1" cd "$repo_path" 2>/dev/null || return 1 if [ -f "tsconfig.app.json" ]; then echo "npx tsc --project tsconfig.app.json --noEmit" elif [ -f "tsconfig.build.json" ]; then echo "npx tsc --project tsconfig.build.json --noEmit" elif [ -f "tsconfig.json" ]; then if grep -q '"references"' tsconfig.json 2>/dev/null; then if [ -f "tsconfig.app.json" ]; then echo "npx tsc --project tsconfig.app.json --noEmit" elif [ -f "tsconfig.src.json" ]; then echo "npx tsc --project tsconfig.src.json --noEmit" else echo "npx tsc --build --noEmit" fi else echo "npx tsc --noEmit" fi else echo "npx tsc --noEmit" fi } # Function to run TSC check run_tsc_check() { local repo="$1" local repo_path="$CLAUDE_PROJECT_DIR/$repo" local cache_file="$CACHE_DIR/$repo-tsc-cmd.cache" cd "$repo_path" 2>/dev/null || return 1 # Get or cache the TSC command for this repo local tsc_cmd if [ -f "$cache_file" ] && [ -z "$FORCE_DETECT" ]; then tsc_cmd=$(cat "$cache_file") else tsc_cmd=$(get_tsc_command "$repo_path") echo "$tsc_cmd" > "$cache_file" fi eval "$tsc_cmd" 2>&1 } # Only process file modification tools case "$TOOL_NAME" in Write|Edit|MultiEdit) # Extract file paths if [ "$TOOL_NAME" = "MultiEdit" ]; then FILE_PATHS=$(echo "$TOOL_INPUT" | jq -r '.edits[].file_path // empty') else FILE_PATHS=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty') fi # Collect repos that need checking (only for TS/JS files) REPOS_TO_CHECK=$(echo "$FILE_PATHS" | grep -E '\.(ts|tsx|js|jsx)$' | while read -r file_path; do if [ -n "$file_path" ]; then repo=$(get_repo_for_file "$file_path") [ -n "$repo" ] && echo "$repo" fi done | sort -u | tr '\n' ' ') # Trim whitespace REPOS_TO_CHECK=$(echo "$REPOS_TO_CHECK" | xargs) if [ -n "$REPOS_TO_CHECK" ]; then ERROR_COUNT=0 ERROR_OUTPUT="" FAILED_REPOS="" # Output to stderr for visibility echo "⚡ TypeScript check on: $REPOS_TO_CHECK" >&2 for repo in $REPOS_TO_CHECK; do echo -n " Checking $repo... " >&2 # Run the check and capture output CHECK_OUTPUT=$(run_tsc_check "$repo" 2>&1) CHECK_EXIT_CODE=$? # Check for TypeScript errors in output if [ $CHECK_EXIT_CODE -ne 0 ] || echo "$CHECK_OUTPUT" | grep -q "error TS"; then echo "❌ Errors found" >&2 ERROR_COUNT=$((ERROR_COUNT + 1)) FAILED_REPOS="$FAILED_REPOS $repo" ERROR_OUTPUT="${ERROR_OUTPUT} === Errors in $repo === $CHECK_OUTPUT" else echo "✅ OK" >&2 fi done # If errors were found, show them and save for agent if [ $ERROR_COUNT -gt 0 ]; then # Save error information for the agent echo "$ERROR_OUTPUT" > "$CACHE_DIR/last-errors.txt" echo "$FAILED_REPOS" > "$CACHE_DIR/affected-repos.txt" # Save the TSC commands used for each repo echo "# TSC Commands by Repo" > "$CACHE_DIR/tsc-commands.txt" for repo in $FAILED_REPOS; do cmd=$(cat "$CACHE_DIR/$repo-tsc-cmd.cache" 2>/dev/null || echo "npx tsc --noEmit") echo "$repo: $cmd" >> "$CACHE_DIR/tsc-commands.txt" done # Output to stderr for visibility { echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "🚨 TypeScript errors found in $ERROR_COUNT repo(s): $FAILED_REPOS" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" echo "👉 IMPORTANT: Use the auto-error-resolver agent to fix the errors" echo "" echo "WE DO NOT LEAVE A MESS BEHIND" echo "Error Preview:" echo "$ERROR_OUTPUT" | grep "error TS" | head -10 echo "" if [ $(echo "$ERROR_OUTPUT" | grep -c "error TS") -gt 10 ]; then echo "... and $(($(echo "$ERROR_OUTPUT" | grep -c "error TS") - 10)) more errors" fi } >&2 # Exit with code 1 to make stderr visible exit 1 fi fi ;; esac # Cleanup old cache directories (older than 7 days) find "$HOME/.claude/tsc-cache" -maxdepth 1 -type d -mtime +7 -exec rm -rf {} \; 2>/dev/null || true exit 0 ================================================ FILE: .claude/hooks/tsconfig.json ================================================ { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "lib": ["ES2022"], "outDir": "./dist", "rootDir": ".", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "types": ["node"] }, "include": ["*.ts"], "exclude": ["node_modules", "dist"] } ================================================ FILE: .claude/settings.json ================================================ { "enableAllProjectMcpServers": true, "enabledMcpjsonServers": [ "mysql", "sequential-thinking", "playwright" ], "permissions": { "allow": [ "Edit:*", "Write:*", "MultiEdit:*", "NotebookEdit:*", "Bash:*" ], "defaultMode": "acceptEdits" }, "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh" } ] } ], "PostToolUse": [ { "matcher": "Edit|MultiEdit|Write", "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/post-tool-use-tracker.sh" } ] } ], "Stop": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/tsc-check.sh" }, { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/trigger-build-resolver.sh" } ] } ] } } ================================================ FILE: .claude/settings.local.json ================================================ { "permissions": { "allow": [ "Bash(for file in /root/git/claude-code-infrastructure-showcase/.claude/skills/frontend-dev-guidelines/resources/*.md)", "Bash(done)" ], "deny": [], "ask": [] } } ================================================ FILE: .claude/skills/README.md ================================================ # Skills Production-tested skills for Claude Code that auto-activate based on context. --- ## What Are Skills? Skills are modular knowledge bases that Claude loads when needed. They provide: - Domain-specific guidelines - Best practices - Code examples - Anti-patterns to avoid **Problem:** Skills don't activate automatically by default. **Solution:** This showcase includes the hooks + configuration to make them activate. --- ## Available Skills ### skill-developer (Meta-Skill) **Purpose:** Creating and managing Claude Code skills **Files:** 7 resource files (426 lines total) **Use when:** - Creating new skills - Understanding skill structure - Working with skill-rules.json - Debugging skill activation **Customization:** ✅ None - copy as-is **[View Skill →](skill-developer/)** --- ### backend-dev-guidelines **Purpose:** Node.js/Express/TypeScript development patterns **Files:** 12 resource files (304 lines main + resources) **Covers:** - Layered architecture (Routes → Controllers → Services → Repositories) - BaseController pattern - Prisma database access - Sentry error tracking - Zod validation - UnifiedConfig pattern - Dependency injection - Testing strategies **Use when:** - Creating/modifying API routes - Building controllers or services - Database operations with Prisma - Setting up error tracking **Customization:** ⚠️ Update `pathPatterns` in skill-rules.json to match your backend directories **Example pathPatterns:** ```json { "pathPatterns": [ "src/api/**/*.ts", // Single app with src/api "backend/**/*.ts", // Backend directory "services/*/src/**/*.ts" // Multi-service monorepo ] } ``` **[View Skill →](backend-dev-guidelines/)** --- ### frontend-dev-guidelines **Purpose:** React/TypeScript/MUI v7 development patterns **Files:** 11 resource files (398 lines main + resources) **Covers:** - Modern React patterns (Suspense, lazy loading) - useSuspenseQuery for data fetching - MUI v7 styling (Grid with `size={{}}` prop) - TanStack Router - File organization (features/ pattern) - Performance optimization - TypeScript best practices **Use when:** - Creating React components - Fetching data with TanStack Query - Styling with MUI v7 - Setting up routing **Customization:** ⚠️ Update `pathPatterns` + verify you use React/MUI **Example pathPatterns:** ```json { "pathPatterns": [ "src/**/*.tsx", // Single React app "frontend/src/**/*.tsx", // Frontend directory "apps/web/**/*.tsx" // Monorepo web app ] } ``` **Note:** This skill is configured as a **guardrail** (enforcement: "block") to prevent MUI v6→v7 incompatibilities. **[View Skill →](frontend-dev-guidelines/)** --- ### route-tester **Purpose:** Testing authenticated API routes with JWT cookie auth **Files:** 1 main file (389 lines) **Covers:** - JWT cookie-based authentication testing - test-auth-route.js script patterns - cURL with cookie authentication - Debugging auth issues - Testing POST/PUT/DELETE operations **Use when:** - Testing API endpoints - Debugging authentication - Validating route functionality **Customization:** ⚠️ Requires JWT cookie auth setup **Ask first:** "Do you use JWT cookie-based authentication?" - If YES: Copy and customize service URLs - If NO: Skip or adapt for your auth method **[View Skill →](route-tester/)** --- ### error-tracking **Purpose:** Sentry error tracking and monitoring patterns **Files:** 1 main file (~250 lines) **Covers:** - Sentry v8 initialization - Error capture patterns - Breadcrumbs and user context - Performance monitoring - Integration with Express and React **Use when:** - Setting up error tracking - Capturing exceptions - Adding error context - Debugging production issues **Customization:** ⚠️ Update `pathPatterns` for your backend **[View Skill →](error-tracking/)** --- ## How to Add a Skill to Your Project ### Quick Integration **For Claude Code:** ``` User: "Add the backend-dev-guidelines skill to my project" Claude should: 1. Ask about project structure 2. Copy skill directory 3. Update skill-rules.json with their paths 4. Verify integration ``` See [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md) for complete instructions. ### Manual Integration **Step 1: Copy the skill directory** ```bash cp -r claude-code-infrastructure-showcase/.claude/skills/backend-dev-guidelines \\ your-project/.claude/skills/ ``` **Step 2: Update skill-rules.json** If you don't have one, create it: ```bash cp claude-code-infrastructure-showcase/.claude/skills/skill-rules.json \\ your-project/.claude/skills/ ``` Then customize the `pathPatterns` for your project: ```json { "skills": { "backend-dev-guidelines": { "fileTriggers": { "pathPatterns": [ "YOUR_BACKEND_PATH/**/*.ts" // ← Update this! ] } } } } ``` **Step 3: Test** - Edit a file in your backend directory - The skill should activate automatically --- ## skill-rules.json Configuration ### What It Does Defines when skills should activate based on: - **Keywords** in user prompts ("backend", "API", "route") - **Intent patterns** (regex matching user intent) - **File path patterns** (editing backend files) - **Content patterns** (code contains Prisma queries) ### Configuration Format ```json { "skill-name": { "type": "domain" | "guardrail", "enforcement": "suggest" | "block", "priority": "high" | "medium" | "low", "promptTriggers": { "keywords": ["list", "of", "keywords"], "intentPatterns": ["regex patterns"] }, "fileTriggers": { "pathPatterns": ["path/to/files/**/*.ts"], "contentPatterns": ["import.*Prisma"] } } } ``` ### Enforcement Levels - **suggest**: Skill appears as suggestion, doesn't block - **block**: Must use skill before proceeding (guardrail) **Use "block" for:** - Preventing breaking changes (MUI v6→v7) - Critical database operations - Security-sensitive code **Use "suggest" for:** - General best practices - Domain guidance - Code organization --- ## Creating Your Own Skills See the **skill-developer** skill for complete guide on: - Skill YAML frontmatter structure - Resource file organization - Trigger pattern design - Testing skill activation **Quick template:** ```markdown --- name: my-skill description: What this skill does --- # My Skill Title ## Purpose [Why this skill exists] ## When to Use This Skill [Auto-activation scenarios] ## Quick Reference [Key patterns and examples] ## Resource Files - [topic-1.md](resources/topic-1.md) - [topic-2.md](resources/topic-2.md) ``` --- ## Troubleshooting ### Skill isn't activating **Check:** 1. Is skill directory in `.claude/skills/`? 2. Is skill listed in `skill-rules.json`? 3. Do `pathPatterns` match your files? 4. Are hooks installed and working? 5. Is settings.json configured correctly? **Debug:** ```bash # Check skill exists ls -la .claude/skills/ # Validate skill-rules.json cat .claude/skills/skill-rules.json | jq . # Check hooks are executable ls -la .claude/hooks/*.sh # Test hook manually ./.claude/hooks/skill-activation-prompt.sh ``` ### Skill activates too often Update skill-rules.json: - Make keywords more specific - Narrow `pathPatterns` - Increase specificity of `intentPatterns` ### Skill never activates Update skill-rules.json: - Add more keywords - Broaden `pathPatterns` - Add more `intentPatterns` --- ## For Claude Code **When integrating a skill for a user:** 1. **Read [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md)** first 2. Ask about their project structure 3. Customize `pathPatterns` in skill-rules.json 4. Verify the skill file has no hardcoded paths 5. Test activation after integration **Common mistakes:** - Keeping example paths (blog-api/, frontend/) - Not asking about monorepo vs single-app - Copying skill-rules.json without customization --- ## Next Steps 1. **Start simple:** Add one skill that matches your work 2. **Verify activation:** Edit a relevant file, skill should suggest 3. **Add more:** Once first skill works, add others 4. **Customize:** Adjust triggers based on your workflow **Questions?** See [CLAUDE_INTEGRATION_GUIDE.md](../../CLAUDE_INTEGRATION_GUIDE.md) for comprehensive integration instructions. ================================================ FILE: .claude/skills/backend-dev-guidelines/SKILL.md ================================================ --- name: backend-dev-guidelines description: Comprehensive backend development guide for Node.js/Express/TypeScript microservices. Use when creating routes, controllers, services, repositories, middleware, or working with Express APIs, Prisma database access, Sentry error tracking, Zod validation, unifiedConfig, dependency injection, or async patterns. Covers layered architecture (routes → controllers → services → repositories), BaseController pattern, error handling, performance monitoring, testing strategies, and migration from legacy patterns. --- # Backend Development Guidelines ## Purpose Establish consistency and best practices across backend microservices (blog-api, auth-service, notifications-service) using modern Node.js/Express/TypeScript patterns. ## When to Use This Skill Automatically activates when working on: - Creating or modifying routes, endpoints, APIs - Building controllers, services, repositories - Implementing middleware (auth, validation, error handling) - Database operations with Prisma - Error tracking with Sentry - Input validation with Zod - Configuration management - Backend testing and refactoring --- ## Quick Start ### New Backend Feature Checklist - [ ] **Route**: Clean definition, delegate to controller - [ ] **Controller**: Extend BaseController - [ ] **Service**: Business logic with DI - [ ] **Repository**: Database access (if complex) - [ ] **Validation**: Zod schema - [ ] **Sentry**: Error tracking - [ ] **Tests**: Unit + integration tests - [ ] **Config**: Use unifiedConfig ### New Microservice Checklist - [ ] Directory structure (see [architecture-overview.md](architecture-overview.md)) - [ ] instrument.ts for Sentry - [ ] unifiedConfig setup - [ ] BaseController class - [ ] Middleware stack - [ ] Error boundary - [ ] Testing framework --- ## Architecture Overview ### Layered Architecture ``` HTTP Request ↓ Routes (routing only) ↓ Controllers (request handling) ↓ Services (business logic) ↓ Repositories (data access) ↓ Database (Prisma) ``` **Key Principle:** Each layer has ONE responsibility. See [architecture-overview.md](architecture-overview.md) for complete details. --- ## Directory Structure ``` service/src/ ├── config/ # UnifiedConfig ├── controllers/ # Request handlers ├── services/ # Business logic ├── repositories/ # Data access ├── routes/ # Route definitions ├── middleware/ # Express middleware ├── types/ # TypeScript types ├── validators/ # Zod schemas ├── utils/ # Utilities ├── tests/ # Tests ├── instrument.ts # Sentry (FIRST IMPORT) ├── app.ts # Express setup └── server.ts # HTTP server ``` **Naming Conventions:** - Controllers: `PascalCase` - `UserController.ts` - Services: `camelCase` - `userService.ts` - Routes: `camelCase + Routes` - `userRoutes.ts` - Repositories: `PascalCase + Repository` - `UserRepository.ts` --- ## Core Principles (7 Key Rules) ### 1. Routes Only Route, Controllers Control ```typescript // ❌ NEVER: Business logic in routes router.post('/submit', async (req, res) => { // 200 lines of logic }); // ✅ ALWAYS: Delegate to controller router.post('/submit', (req, res) => controller.submit(req, res)); ``` ### 2. All Controllers Extend BaseController ```typescript export class UserController extends BaseController { async getUser(req: Request, res: Response): Promise { try { const user = await this.userService.findById(req.params.id); this.handleSuccess(res, user); } catch (error) { this.handleError(error, res, 'getUser'); } } } ``` ### 3. All Errors to Sentry ```typescript try { await operation(); } catch (error) { Sentry.captureException(error); throw error; } ``` ### 4. Use unifiedConfig, NEVER process.env ```typescript // ❌ NEVER const timeout = process.env.TIMEOUT_MS; // ✅ ALWAYS import { config } from './config/unifiedConfig'; const timeout = config.timeouts.default; ``` ### 5. Validate All Input with Zod ```typescript const schema = z.object({ email: z.string().email() }); const validated = schema.parse(req.body); ``` ### 6. Use Repository Pattern for Data Access ```typescript // Service → Repository → Database const users = await userRepository.findActive(); ``` ### 7. Comprehensive Testing Required ```typescript describe('UserService', () => { it('should create user', async () => { expect(user).toBeDefined(); }); }); ``` --- ## Common Imports ```typescript // Express import express, { Request, Response, NextFunction, Router } from 'express'; // Validation import { z } from 'zod'; // Database import { PrismaClient } from '@prisma/client'; import type { Prisma } from '@prisma/client'; // Sentry import * as Sentry from '@sentry/node'; // Config import { config } from './config/unifiedConfig'; // Middleware import { SSOMiddlewareClient } from './middleware/SSOMiddleware'; import { asyncErrorWrapper } from './middleware/errorBoundary'; ``` --- ## Quick Reference ### HTTP Status Codes | Code | Use Case | |------|----------| | 200 | Success | | 201 | Created | | 400 | Bad Request | | 401 | Unauthorized | | 403 | Forbidden | | 404 | Not Found | | 500 | Server Error | ### Service Templates **Blog API** (✅ Mature) - Use as template for REST APIs **Auth Service** (✅ Mature) - Use as template for authentication patterns --- ## Anti-Patterns to Avoid ❌ Business logic in routes ❌ Direct process.env usage ❌ Missing error handling ❌ No input validation ❌ Direct Prisma everywhere ❌ console.log instead of Sentry --- ## Navigation Guide | Need to... | Read this | |------------|-----------| | Understand architecture | [architecture-overview.md](architecture-overview.md) | | Create routes/controllers | [routing-and-controllers.md](routing-and-controllers.md) | | Organize business logic | [services-and-repositories.md](services-and-repositories.md) | | Validate input | [validation-patterns.md](validation-patterns.md) | | Add error tracking | [sentry-and-monitoring.md](sentry-and-monitoring.md) | | Create middleware | [middleware-guide.md](middleware-guide.md) | | Database access | [database-patterns.md](database-patterns.md) | | Manage config | [configuration.md](configuration.md) | | Handle async/errors | [async-and-errors.md](async-and-errors.md) | | Write tests | [testing-guide.md](testing-guide.md) | | See examples | [complete-examples.md](complete-examples.md) | --- ## Resource Files ### [architecture-overview.md](architecture-overview.md) Layered architecture, request lifecycle, separation of concerns ### [routing-and-controllers.md](routing-and-controllers.md) Route definitions, BaseController, error handling, examples ### [services-and-repositories.md](services-and-repositories.md) Service patterns, DI, repository pattern, caching ### [validation-patterns.md](validation-patterns.md) Zod schemas, validation, DTO pattern ### [sentry-and-monitoring.md](sentry-and-monitoring.md) Sentry init, error capture, performance monitoring ### [middleware-guide.md](middleware-guide.md) Auth, audit, error boundaries, AsyncLocalStorage ### [database-patterns.md](database-patterns.md) PrismaService, repositories, transactions, optimization ### [configuration.md](configuration.md) UnifiedConfig, environment configs, secrets ### [async-and-errors.md](async-and-errors.md) Async patterns, custom errors, asyncErrorWrapper ### [testing-guide.md](testing-guide.md) Unit/integration tests, mocking, coverage ### [complete-examples.md](complete-examples.md) Full examples, refactoring guide --- ## Related Skills - **database-verification** - Verify column names and schema consistency - **error-tracking** - Sentry integration patterns - **skill-developer** - Meta-skill for creating and managing skills --- **Skill Status**: COMPLETE ✅ **Line Count**: < 500 ✅ **Progressive Disclosure**: 11 resource files ✅ ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/architecture-overview.md ================================================ # Architecture Overview - Backend Services Complete guide to the layered architecture pattern used in backend microservices. ## Table of Contents - [Layered Architecture Pattern](#layered-architecture-pattern) - [Request Lifecycle](#request-lifecycle) - [Service Comparison](#service-comparison) - [Directory Structure Rationale](#directory-structure-rationale) - [Module Organization](#module-organization) - [Separation of Concerns](#separation-of-concerns) --- ## Layered Architecture Pattern ### The Four Layers ``` ┌─────────────────────────────────────┐ │ HTTP Request │ └───────────────┬─────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Layer 1: ROUTES │ │ - Route definitions only │ │ - Middleware registration │ │ - Delegate to controllers │ │ - NO business logic │ └───────────────┬─────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Layer 2: CONTROLLERS │ │ - Request/response handling │ │ - Input validation │ │ - Call services │ │ - Format responses │ │ - Error handling │ └───────────────┬─────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Layer 3: SERVICES │ │ - Business logic │ │ - Orchestration │ │ - Call repositories │ │ - No HTTP knowledge │ └───────────────┬─────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Layer 4: REPOSITORIES │ │ - Data access abstraction │ │ - Prisma operations │ │ - Query optimization │ │ - Caching │ └───────────────┬─────────────────────┘ ↓ ┌─────────────────────────────────────┐ │ Database (MySQL) │ └─────────────────────────────────────┘ ``` ### Why This Architecture? **Testability:** - Each layer can be tested independently - Easy to mock dependencies - Clear test boundaries **Maintainability:** - Changes isolated to specific layers - Business logic separate from HTTP concerns - Easy to locate bugs **Reusability:** - Services can be used by routes, cron jobs, scripts - Repositories hide database implementation - Business logic not tied to HTTP **Scalability:** - Easy to add new endpoints - Clear patterns to follow - Consistent structure --- ## Request Lifecycle ### Complete Flow Example ```typescript 1. HTTP POST /api/users ↓ 2. Express matches route in userRoutes.ts ↓ 3. Middleware chain executes: - SSOMiddleware.verifyLoginStatus (authentication) - auditMiddleware (context tracking) ↓ 4. Route handler delegates to controller: router.post('/users', (req, res) => userController.create(req, res)) ↓ 5. Controller validates and calls service: - Validate input with Zod - Call userService.create(data) - Handle success/error ↓ 6. Service executes business logic: - Check business rules - Call userRepository.create(data) - Return result ↓ 7. Repository performs database operation: - PrismaService.main.user.create({ data }) - Handle database errors - Return created user ↓ 8. Response flows back: Repository → Service → Controller → Express → Client ``` ### Middleware Execution Order **Critical:** Middleware executes in registration order ```typescript app.use(Sentry.Handlers.requestHandler()); // 1. Sentry tracing (FIRST) app.use(express.json()); // 2. Body parsing app.use(express.urlencoded({ extended: true })); // 3. URL encoding app.use(cookieParser()); // 4. Cookie parsing app.use(SSOMiddleware.initialize()); // 5. Auth initialization // ... routes registered here app.use(auditMiddleware); // 6. Audit (if global) app.use(errorBoundary); // 7. Error handler (LAST) app.use(Sentry.Handlers.errorHandler()); // 8. Sentry errors (LAST) ``` **Rule:** Error handlers must be registered AFTER routes! --- ## Service Comparison ### Email Service (Mature Pattern ✅) **Strengths:** - Comprehensive BaseController with Sentry integration - Clean route delegation (no business logic in routes) - Consistent dependency injection pattern - Good middleware organization - Type-safe throughout - Excellent error handling **Example Structure:** ``` email/src/ ├── controllers/ │ ├── BaseController.ts ✅ Excellent template │ ├── NotificationController.ts ✅ Extends BaseController │ └── EmailController.ts ✅ Clean patterns ├── routes/ │ ├── notificationRoutes.ts ✅ Clean delegation │ └── emailRoutes.ts ✅ No business logic ├── services/ │ ├── NotificationService.ts ✅ Dependency injection │ └── BatchingService.ts ✅ Clear responsibility └── middleware/ ├── errorBoundary.ts ✅ Comprehensive └── DevImpersonationSSOMiddleware.ts ``` **Use as template** for new services! ### Form Service (Transitioning ⚠️) **Strengths:** - Excellent workflow architecture (event sourcing) - Good Sentry integration - Innovative audit middleware (AsyncLocalStorage) - Comprehensive permission system **Weaknesses:** - Some routes have 200+ lines of business logic - Inconsistent controller naming - Direct process.env usage (60+ occurrences) - Minimal repository pattern usage **Example:** ``` form/src/ ├── routes/ │ ├── responseRoutes.ts ❌ Business logic in routes │ └── proxyRoutes.ts ✅ Good validation pattern ├── controllers/ │ ├── formController.ts ⚠️ Lowercase naming │ └── UserProfileController.ts ✅ PascalCase naming ├── workflow/ ✅ Excellent architecture! │ ├── core/ │ │ ├── WorkflowEngineV3.ts ✅ Event sourcing │ │ └── DryRunWrapper.ts ✅ Innovative │ └── services/ └── middleware/ └── auditMiddleware.ts ✅ AsyncLocalStorage pattern ``` **Learn from:** workflow/, middleware/auditMiddleware.ts **Avoid:** responseRoutes.ts, direct process.env --- ## Directory Structure Rationale ### Controllers Directory **Purpose:** Handle HTTP request/response concerns **Contents:** - `BaseController.ts` - Base class with common methods - `{Feature}Controller.ts` - Feature-specific controllers **Naming:** PascalCase + Controller **Responsibilities:** - Parse request parameters - Validate input (Zod) - Call appropriate service methods - Format responses - Handle errors (via BaseController) - Set HTTP status codes ### Services Directory **Purpose:** Business logic and orchestration **Contents:** - `{feature}Service.ts` - Feature business logic **Naming:** camelCase + Service (or PascalCase + Service) **Responsibilities:** - Implement business rules - Orchestrate multiple repositories - Transaction management - Business validations - No HTTP knowledge (Request/Response types) ### Repositories Directory **Purpose:** Data access abstraction **Contents:** - `{Entity}Repository.ts` - Database operations for entity **Naming:** PascalCase + Repository **Responsibilities:** - Prisma query operations - Query optimization - Database error handling - Caching layer - Hide Prisma implementation details **Current Gap:** Only 1 repository exists (WorkflowRepository) ### Routes Directory **Purpose:** Route registration ONLY **Contents:** - `{feature}Routes.ts` - Express router for feature **Naming:** camelCase + Routes **Responsibilities:** - Register routes with Express - Apply middleware - Delegate to controllers - **NO business logic!** ### Middleware Directory **Purpose:** Cross-cutting concerns **Contents:** - Authentication middleware - Audit middleware - Error boundaries - Validation middleware - Custom middleware **Naming:** camelCase **Types:** - Request processing (before handler) - Response processing (after handler) - Error handling (error boundary) ### Config Directory **Purpose:** Configuration management **Contents:** - `unifiedConfig.ts` - Type-safe configuration - Environment-specific configs **Pattern:** Single source of truth ### Types Directory **Purpose:** TypeScript type definitions **Contents:** - `{feature}.types.ts` - Feature-specific types - DTOs (Data Transfer Objects) - Request/Response types - Domain models --- ## Module Organization ### Feature-Based Organization For large features, use subdirectories: ``` src/workflow/ ├── core/ # Core engine ├── services/ # Workflow-specific services ├── actions/ # System actions ├── models/ # Domain models ├── validators/ # Workflow validation └── utils/ # Workflow utilities ``` **When to use:** - Feature has 5+ files - Clear sub-domains exist - Logical grouping improves clarity ### Flat Organization For simple features: ``` src/ ├── controllers/UserController.ts ├── services/userService.ts ├── routes/userRoutes.ts └── repositories/UserRepository.ts ``` **When to use:** - Simple features (< 5 files) - No clear sub-domains - Flat structure is clearer --- ## Separation of Concerns ### What Goes Where **Routes Layer:** - ✅ Route definitions - ✅ Middleware registration - ✅ Controller delegation - ❌ Business logic - ❌ Database operations - ❌ Validation logic (should be in validator or controller) **Controllers Layer:** - ✅ Request parsing (params, body, query) - ✅ Input validation (Zod) - ✅ Service calls - ✅ Response formatting - ✅ Error handling - ❌ Business logic - ❌ Database operations **Services Layer:** - ✅ Business logic - ✅ Business rules enforcement - ✅ Orchestration (multiple repos) - ✅ Transaction management - ❌ HTTP concerns (Request/Response) - ❌ Direct Prisma calls (use repositories) **Repositories Layer:** - ✅ Prisma operations - ✅ Query construction - ✅ Database error handling - ✅ Caching - ❌ Business logic - ❌ HTTP concerns ### Example: User Creation **Route:** ```typescript router.post('/users', SSOMiddleware.verifyLoginStatus, auditMiddleware, (req, res) => userController.create(req, res) ); ``` **Controller:** ```typescript async create(req: Request, res: Response): Promise { try { const validated = createUserSchema.parse(req.body); const user = await this.userService.create(validated); this.handleSuccess(res, user, 'User created'); } catch (error) { this.handleError(error, res, 'create'); } } ``` **Service:** ```typescript async create(data: CreateUserDTO): Promise { // Business rule: check if email already exists const existing = await this.userRepository.findByEmail(data.email); if (existing) throw new ConflictError('Email already exists'); // Create user return await this.userRepository.create(data); } ``` **Repository:** ```typescript async create(data: CreateUserDTO): Promise { return PrismaService.main.user.create({ data }); } async findByEmail(email: string): Promise { return PrismaService.main.user.findUnique({ where: { email } }); } ``` **Notice:** Each layer has clear, distinct responsibilities! --- **Related Files:** - [SKILL.md](SKILL.md) - Main guide - [routing-and-controllers.md](routing-and-controllers.md) - Routes and controllers details - [services-and-repositories.md](services-and-repositories.md) - Service and repository patterns ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/async-and-errors.md ================================================ # Async Patterns and Error Handling Complete guide to async/await patterns and custom error handling. ## Table of Contents - [Async/Await Best Practices](#asyncawait-best-practices) - [Promise Error Handling](#promise-error-handling) - [Custom Error Types](#custom-error-types) - [asyncErrorWrapper Utility](#asyncerrorwrapper-utility) - [Error Propagation](#error-propagation) - [Common Async Pitfalls](#common-async-pitfalls) --- ## Async/Await Best Practices ### Always Use Try-Catch ```typescript // ❌ NEVER: Unhandled async errors async function fetchData() { const data = await database.query(); // If throws, unhandled! return data; } // ✅ ALWAYS: Wrap in try-catch async function fetchData() { try { const data = await database.query(); return data; } catch (error) { Sentry.captureException(error); throw error; } } ``` ### Avoid .then() Chains ```typescript // ❌ AVOID: Promise chains function processData() { return fetchData() .then(data => transform(data)) .then(transformed => save(transformed)) .catch(error => { console.error(error); }); } // ✅ PREFER: Async/await async function processData() { try { const data = await fetchData(); const transformed = await transform(data); return await save(transformed); } catch (error) { Sentry.captureException(error); throw error; } } ``` --- ## Promise Error Handling ### Parallel Operations ```typescript // ✅ Handle errors in Promise.all try { const [users, profiles, settings] = await Promise.all([ userService.getAll(), profileService.getAll(), settingsService.getAll(), ]); } catch (error) { // One failure fails all Sentry.captureException(error); throw error; } // ✅ Handle errors individually with Promise.allSettled const results = await Promise.allSettled([ userService.getAll(), profileService.getAll(), settingsService.getAll(), ]); results.forEach((result, index) => { if (result.status === 'rejected') { Sentry.captureException(result.reason, { tags: { operation: ['users', 'profiles', 'settings'][index] } }); } }); ``` --- ## Custom Error Types ### Define Custom Errors ```typescript // Base error class export class AppError extends Error { constructor( message: string, public code: string, public statusCode: number, public isOperational: boolean = true ) { super(message); this.name = this.constructor.name; Error.captureStackTrace(this, this.constructor); } } // Specific error types export class ValidationError extends AppError { constructor(message: string) { super(message, 'VALIDATION_ERROR', 400); } } export class NotFoundError extends AppError { constructor(message: string) { super(message, 'NOT_FOUND', 404); } } export class ForbiddenError extends AppError { constructor(message: string) { super(message, 'FORBIDDEN', 403); } } export class ConflictError extends AppError { constructor(message: string) { super(message, 'CONFLICT', 409); } } ``` ### Usage ```typescript // Throw specific errors if (!user) { throw new NotFoundError('User not found'); } if (user.age < 18) { throw new ValidationError('User must be 18+'); } // Error boundary handles them function errorBoundary(error, req, res, next) { if (error instanceof AppError) { return res.status(error.statusCode).json({ error: { message: error.message, code: error.code } }); } // Unknown error Sentry.captureException(error); res.status(500).json({ error: { message: 'Internal server error' } }); } ``` --- ## asyncErrorWrapper Utility ### Pattern ```typescript export function asyncErrorWrapper( handler: (req: Request, res: Response, next: NextFunction) => Promise ) { return async (req: Request, res: Response, next: NextFunction) => { try { await handler(req, res, next); } catch (error) { next(error); } }; } ``` ### Usage ```typescript // Without wrapper - error can be unhandled router.get('/users', async (req, res) => { const users = await userService.getAll(); // If throws, unhandled! res.json(users); }); // With wrapper - errors caught router.get('/users', asyncErrorWrapper(async (req, res) => { const users = await userService.getAll(); res.json(users); })); ``` --- ## Error Propagation ### Proper Error Chains ```typescript // ✅ Propagate errors up the stack async function repositoryMethod() { try { return await PrismaService.main.user.findMany(); } catch (error) { Sentry.captureException(error, { tags: { layer: 'repository' } }); throw error; // Propagate to service } } async function serviceMethod() { try { return await repositoryMethod(); } catch (error) { Sentry.captureException(error, { tags: { layer: 'service' } }); throw error; // Propagate to controller } } async function controllerMethod(req, res) { try { const result = await serviceMethod(); res.json(result); } catch (error) { this.handleError(error, res, 'controllerMethod'); // Final handler } } ``` --- ## Common Async Pitfalls ### Fire and Forget (Bad) ```typescript // ❌ NEVER: Fire and forget async function processRequest(req, res) { sendEmail(user.email); // Fires async, errors unhandled! res.json({ success: true }); } // ✅ ALWAYS: Await or handle async function processRequest(req, res) { try { await sendEmail(user.email); res.json({ success: true }); } catch (error) { Sentry.captureException(error); res.status(500).json({ error: 'Failed to send email' }); } } // ✅ OR: Intentional background task async function processRequest(req, res) { sendEmail(user.email).catch(error => { Sentry.captureException(error); }); res.json({ success: true }); } ``` ### Unhandled Rejections ```typescript // ✅ Global handler for unhandled rejections process.on('unhandledRejection', (reason, promise) => { Sentry.captureException(reason, { tags: { type: 'unhandled_rejection' } }); console.error('Unhandled Rejection:', reason); }); process.on('uncaughtException', (error) => { Sentry.captureException(error, { tags: { type: 'uncaught_exception' } }); console.error('Uncaught Exception:', error); process.exit(1); }); ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [sentry-and-monitoring.md](sentry-and-monitoring.md) - [complete-examples.md](complete-examples.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/complete-examples.md ================================================ # Complete Examples - Full Working Code Real-world examples showing complete implementation patterns. ## Table of Contents - [Complete Controller Example](#complete-controller-example) - [Complete Service with DI](#complete-service-with-di) - [Complete Route File](#complete-route-file) - [Complete Repository](#complete-repository) - [Refactoring Example: Bad to Good](#refactoring-example-bad-to-good) - [End-to-End Feature Example](#end-to-end-feature-example) --- ## Complete Controller Example ### UserController (Following All Best Practices) ```typescript // controllers/UserController.ts import { Request, Response } from 'express'; import { BaseController } from './BaseController'; import { UserService } from '../services/userService'; import { createUserSchema, updateUserSchema } from '../validators/userSchemas'; import { z } from 'zod'; export class UserController extends BaseController { private userService: UserService; constructor() { super(); this.userService = new UserService(); } async getUser(req: Request, res: Response): Promise { try { this.addBreadcrumb('Fetching user', 'user_controller', { userId: req.params.id, }); const user = await this.withTransaction( 'user.get', 'db.query', () => this.userService.findById(req.params.id) ); if (!user) { return this.handleError( new Error('User not found'), res, 'getUser', 404 ); } this.handleSuccess(res, user); } catch (error) { this.handleError(error, res, 'getUser'); } } async listUsers(req: Request, res: Response): Promise { try { const users = await this.userService.getAll(); this.handleSuccess(res, users); } catch (error) { this.handleError(error, res, 'listUsers'); } } async createUser(req: Request, res: Response): Promise { try { // Validate input with Zod const validated = createUserSchema.parse(req.body); // Track performance const user = await this.withTransaction( 'user.create', 'db.mutation', () => this.userService.create(validated) ); this.handleSuccess(res, user, 'User created successfully', 201); } catch (error) { if (error instanceof z.ZodError) { return this.handleError(error, res, 'createUser', 400); } this.handleError(error, res, 'createUser'); } } async updateUser(req: Request, res: Response): Promise { try { const validated = updateUserSchema.parse(req.body); const user = await this.userService.update( req.params.id, validated ); this.handleSuccess(res, user, 'User updated'); } catch (error) { if (error instanceof z.ZodError) { return this.handleError(error, res, 'updateUser', 400); } this.handleError(error, res, 'updateUser'); } } async deleteUser(req: Request, res: Response): Promise { try { await this.userService.delete(req.params.id); this.handleSuccess(res, null, 'User deleted', 204); } catch (error) { this.handleError(error, res, 'deleteUser'); } } } ``` --- ## Complete Service with DI ### UserService ```typescript // services/userService.ts import { UserRepository } from '../repositories/UserRepository'; import { ConflictError, NotFoundError, ValidationError } from '../types/errors'; import type { CreateUserDTO, UpdateUserDTO, User } from '../types/user.types'; export class UserService { private userRepository: UserRepository; constructor(userRepository?: UserRepository) { this.userRepository = userRepository || new UserRepository(); } async findById(id: string): Promise { return await this.userRepository.findById(id); } async getAll(): Promise { return await this.userRepository.findActive(); } async create(data: CreateUserDTO): Promise { // Business rule: validate age if (data.age < 18) { throw new ValidationError('User must be 18 or older'); } // Business rule: check email uniqueness const existing = await this.userRepository.findByEmail(data.email); if (existing) { throw new ConflictError('Email already in use'); } // Create user with profile return await this.userRepository.create({ email: data.email, profile: { create: { firstName: data.firstName, lastName: data.lastName, age: data.age, }, }, }); } async update(id: string, data: UpdateUserDTO): Promise { // Check exists const existing = await this.userRepository.findById(id); if (!existing) { throw new NotFoundError('User not found'); } // Business rule: email uniqueness if changing if (data.email && data.email !== existing.email) { const emailTaken = await this.userRepository.findByEmail(data.email); if (emailTaken) { throw new ConflictError('Email already in use'); } } return await this.userRepository.update(id, data); } async delete(id: string): Promise { const existing = await this.userRepository.findById(id); if (!existing) { throw new NotFoundError('User not found'); } await this.userRepository.delete(id); } } ``` --- ## Complete Route File ### userRoutes.ts ```typescript // routes/userRoutes.ts import { Router } from 'express'; import { UserController } from '../controllers/UserController'; import { SSOMiddlewareClient } from '../middleware/SSOMiddleware'; import { auditMiddleware } from '../middleware/auditMiddleware'; const router = Router(); const controller = new UserController(); // GET /users - List all users router.get('/', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.listUsers(req, res) ); // GET /users/:id - Get single user router.get('/:id', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.getUser(req, res) ); // POST /users - Create user router.post('/', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.createUser(req, res) ); // PUT /users/:id - Update user router.put('/:id', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.updateUser(req, res) ); // DELETE /users/:id - Delete user router.delete('/:id', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.deleteUser(req, res) ); export default router; ``` --- ## Complete Repository ### UserRepository ```typescript // repositories/UserRepository.ts import { PrismaService } from '@project-lifecycle-portal/database'; import type { User, Prisma } from '@prisma/client'; export class UserRepository { async findById(id: string): Promise { return PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); } async findByEmail(email: string): Promise { return PrismaService.main.user.findUnique({ where: { email }, include: { profile: true }, }); } async findActive(): Promise { return PrismaService.main.user.findMany({ where: { isActive: true }, include: { profile: true }, orderBy: { createdAt: 'desc' }, }); } async create(data: Prisma.UserCreateInput): Promise { return PrismaService.main.user.create({ data, include: { profile: true }, }); } async update(id: string, data: Prisma.UserUpdateInput): Promise { return PrismaService.main.user.update({ where: { id }, data, include: { profile: true }, }); } async delete(id: string): Promise { // Soft delete return PrismaService.main.user.update({ where: { id }, data: { isActive: false, deletedAt: new Date(), }, }); } } ``` --- ## Refactoring Example: Bad to Good ### BEFORE: Business Logic in Routes ❌ ```typescript // routes/postRoutes.ts (BAD - 200+ lines) router.post('/posts', async (req, res) => { try { const username = res.locals.claims.preferred_username; const responses = req.body.responses; const stepInstanceId = req.body.stepInstanceId; // ❌ Permission check in route const userId = await userProfileService.getProfileByEmail(username).then(p => p.id); const canComplete = await permissionService.canCompleteStep(userId, stepInstanceId); if (!canComplete) { return res.status(403).json({ error: 'No permission' }); } // ❌ Business logic in route const post = await postRepository.create({ title: req.body.title, content: req.body.content, authorId: userId }); // ❌ More business logic... if (res.locals.isImpersonating) { impersonationContextStore.storeContext(...); } // ... 100+ more lines res.json({ success: true, data: result }); } catch (e) { handler.handleException(res, e); } }); ``` ### AFTER: Clean Separation ✅ **1. Clean Route:** ```typescript // routes/postRoutes.ts import { PostController } from '../controllers/PostController'; const router = Router(); const controller = new PostController(); // ✅ CLEAN: 8 lines total! router.post('/', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.createPost(req, res) ); export default router; ``` **2. Controller:** ```typescript // controllers/PostController.ts export class PostController extends BaseController { private postService: PostService; constructor() { super(); this.postService = new PostService(); } async createPost(req: Request, res: Response): Promise { try { const validated = createPostSchema.parse({ ...req.body, }); const result = await this.postService.createPost( validated, res.locals.userId ); this.handleSuccess(res, result, 'Post created successfully'); } catch (error) { this.handleError(error, res, 'createPost'); } } } ``` **3. Service:** ```typescript // services/postService.ts export class PostService { async createPost( data: CreatePostDTO, userId: string ): Promise { // Permission check const canComplete = await permissionService.canCompleteStep( userId, data.stepInstanceId ); if (!canComplete) { throw new ForbiddenError('No permission to complete step'); } // Execute workflow const engine = await createWorkflowEngine(); const command = new CompleteStepCommand( data.stepInstanceId, userId, data.responses ); const events = await engine.executeCommand(command); // Handle impersonation if (context.isImpersonating) { await this.handleImpersonation(data.stepInstanceId, context); } return { events, success: true }; } private async handleImpersonation(stepInstanceId: number, context: any) { impersonationContextStore.storeContext(stepInstanceId, { originalUserId: context.originalUserId, effectiveUserId: context.effectiveUserId, }); } } ``` **Result:** - Route: 8 lines (was 200+) - Controller: 25 lines - Service: 40 lines - **Testable, maintainable, reusable!** --- ## End-to-End Feature Example ### Complete User Management Feature **1. Types:** ```typescript // types/user.types.ts export interface User { id: string; email: string; isActive: boolean; profile?: UserProfile; } export interface CreateUserDTO { email: string; firstName: string; lastName: string; age: number; } export interface UpdateUserDTO { email?: string; firstName?: string; lastName?: string; } ``` **2. Validators:** ```typescript // validators/userSchemas.ts import { z } from 'zod'; export const createUserSchema = z.object({ email: z.string().email(), firstName: z.string().min(1).max(100), lastName: z.string().min(1).max(100), age: z.number().int().min(18).max(120), }); export const updateUserSchema = z.object({ email: z.string().email().optional(), firstName: z.string().min(1).max(100).optional(), lastName: z.string().min(1).max(100).optional(), }); ``` **3. Repository:** ```typescript // repositories/UserRepository.ts export class UserRepository { async findById(id: string): Promise { return PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); } async create(data: Prisma.UserCreateInput): Promise { return PrismaService.main.user.create({ data, include: { profile: true }, }); } } ``` **4. Service:** ```typescript // services/userService.ts export class UserService { private userRepository: UserRepository; constructor() { this.userRepository = new UserRepository(); } async create(data: CreateUserDTO): Promise { const existing = await this.userRepository.findByEmail(data.email); if (existing) { throw new ConflictError('Email already exists'); } return await this.userRepository.create({ email: data.email, profile: { create: { firstName: data.firstName, lastName: data.lastName, age: data.age, }, }, }); } } ``` **5. Controller:** ```typescript // controllers/UserController.ts export class UserController extends BaseController { private userService: UserService; constructor() { super(); this.userService = new UserService(); } async createUser(req: Request, res: Response): Promise { try { const validated = createUserSchema.parse(req.body); const user = await this.userService.create(validated); this.handleSuccess(res, user, 'User created', 201); } catch (error) { this.handleError(error, res, 'createUser'); } } } ``` **6. Routes:** ```typescript // routes/userRoutes.ts const router = Router(); const controller = new UserController(); router.post('/', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => controller.createUser(req, res) ); export default router; ``` **7. Register in app.ts:** ```typescript // app.ts import userRoutes from './routes/userRoutes'; app.use('/api/users', userRoutes); ``` **Complete Request Flow:** ``` POST /api/users ↓ userRoutes matches / ↓ SSOMiddleware authenticates ↓ controller.createUser called ↓ Validates with Zod ↓ userService.create called ↓ Checks business rules ↓ userRepository.create called ↓ Prisma creates user ↓ Returns up the chain ↓ Controller formats response ↓ 200/201 sent to client ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [routing-and-controllers.md](routing-and-controllers.md) - [services-and-repositories.md](services-and-repositories.md) - [validation-patterns.md](validation-patterns.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/configuration.md ================================================ # Configuration Management - UnifiedConfig Pattern Complete guide to managing configuration in backend microservices. ## Table of Contents - [UnifiedConfig Overview](#unifiedconfig-overview) - [NEVER Use process.env Directly](#never-use-processenv-directly) - [Configuration Structure](#configuration-structure) - [Environment-Specific Configs](#environment-specific-configs) - [Secrets Management](#secrets-management) - [Migration Guide](#migration-guide) --- ## UnifiedConfig Overview ### Why UnifiedConfig? **Problems with process.env:** - ❌ No type safety - ❌ No validation - ❌ Hard to test - ❌ Scattered throughout code - ❌ No default values - ❌ Runtime errors for typos **Benefits of unifiedConfig:** - ✅ Type-safe configuration - ✅ Single source of truth - ✅ Validated at startup - ✅ Easy to test with mocks - ✅ Clear structure - ✅ Fallback to environment variables --- ## NEVER Use process.env Directly ### The Rule ```typescript // ❌ NEVER DO THIS const timeout = parseInt(process.env.TIMEOUT_MS || '5000'); const dbHost = process.env.DB_HOST || 'localhost'; // ✅ ALWAYS DO THIS import { config } from './config/unifiedConfig'; const timeout = config.timeouts.default; const dbHost = config.database.host; ``` ### Why This Matters **Example of problems:** ```typescript // Typo in environment variable name const host = process.env.DB_HSOT; // undefined! No error! // Type safety const port = process.env.PORT; // string! Need parseInt const timeout = parseInt(process.env.TIMEOUT); // NaN if not set! ``` **With unifiedConfig:** ```typescript const port = config.server.port; // number, guaranteed const timeout = config.timeouts.default; // number, with fallback ``` --- ## Configuration Structure ### UnifiedConfig Interface ```typescript export interface UnifiedConfig { database: { host: string; port: number; username: string; password: string; database: string; }; server: { port: number; sessionSecret: string; }; tokens: { jwt: string; inactivity: string; internal: string; }; keycloak: { realm: string; client: string; baseUrl: string; secret: string; }; aws: { region: string; emailQueueUrl: string; accessKeyId: string; secretAccessKey: string; }; sentry: { dsn: string; environment: string; tracesSampleRate: number; }; // ... more sections } ``` ### Implementation Pattern **File:** `/blog-api/src/config/unifiedConfig.ts` ```typescript import * as fs from 'fs'; import * as path from 'path'; import * as ini from 'ini'; const configPath = path.join(__dirname, '../../config.ini'); const iniConfig = ini.parse(fs.readFileSync(configPath, 'utf-8')); export const config: UnifiedConfig = { database: { host: iniConfig.database?.host || process.env.DB_HOST || 'localhost', port: parseInt(iniConfig.database?.port || process.env.DB_PORT || '3306'), username: iniConfig.database?.username || process.env.DB_USER || 'root', password: iniConfig.database?.password || process.env.DB_PASSWORD || '', database: iniConfig.database?.database || process.env.DB_NAME || 'blog_dev', }, server: { port: parseInt(iniConfig.server?.port || process.env.PORT || '3002'), sessionSecret: iniConfig.server?.sessionSecret || process.env.SESSION_SECRET || 'dev-secret', }, // ... more configuration }; // Validate critical config if (!config.tokens.jwt) { throw new Error('JWT secret not configured!'); } ``` **Key Points:** - Read from config.ini first - Fallback to process.env - Default values for development - Validation at startup - Type-safe access --- ## Environment-Specific Configs ### config.ini Structure ```ini [database] host = localhost port = 3306 username = root password = password1 database = blog_dev [server] port = 3002 sessionSecret = your-secret-here [tokens] jwt = your-jwt-secret inactivity = 30m internal = internal-api-token [keycloak] realm = myapp client = myapp-client baseUrl = http://localhost:8080 secret = keycloak-client-secret [sentry] dsn = https://your-sentry-dsn environment = development tracesSampleRate = 0.1 ``` ### Environment Overrides ```bash # .env file (optional overrides) DB_HOST=production-db.example.com DB_PASSWORD=secure-password PORT=80 ``` **Precedence:** 1. config.ini (highest priority) 2. process.env variables 3. Hard-coded defaults (lowest priority) --- ## Secrets Management ### DO NOT Commit Secrets ```gitignore # .gitignore config.ini .env sentry.ini *.pem *.key ``` ### Use Environment Variables in Production ```typescript // Development: config.ini // Production: Environment variables export const config: UnifiedConfig = { database: { password: process.env.DB_PASSWORD || iniConfig.database?.password || '', }, tokens: { jwt: process.env.JWT_SECRET || iniConfig.tokens?.jwt || '', }, }; ``` --- ## Migration Guide ### Find All process.env Usage ```bash grep -r "process.env" blog-api/src/ --include="*.ts" | wc -l ``` ### Migration Example **Before:** ```typescript // Scattered throughout code const timeout = parseInt(process.env.OPENID_HTTP_TIMEOUT_MS || '15000'); const keycloakUrl = process.env.KEYCLOAK_BASE_URL; const jwtSecret = process.env.JWT_SECRET; ``` **After:** ```typescript import { config } from './config/unifiedConfig'; const timeout = config.keycloak.timeout; const keycloakUrl = config.keycloak.baseUrl; const jwtSecret = config.tokens.jwt; ``` **Benefits:** - Type-safe - Centralized - Easy to test - Validated at startup --- **Related Files:** - [SKILL.md](SKILL.md) - [testing-guide.md](testing-guide.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/database-patterns.md ================================================ # Database Patterns - Prisma Best Practices Complete guide to database access patterns using Prisma in backend microservices. ## Table of Contents - [PrismaService Usage](#prismaservice-usage) - [Repository Pattern](#repository-pattern) - [Transaction Patterns](#transaction-patterns) - [Query Optimization](#query-optimization) - [N+1 Query Prevention](#n1-query-prevention) - [Error Handling](#error-handling) --- ## PrismaService Usage ### Basic Pattern ```typescript import { PrismaService } from '@project-lifecycle-portal/database'; // Always use PrismaService.main const users = await PrismaService.main.user.findMany(); ``` ### Check Availability ```typescript if (!PrismaService.isAvailable) { throw new Error('Prisma client not initialized'); } const user = await PrismaService.main.user.findUnique({ where: { id } }); ``` --- ## Repository Pattern ### Why Use Repositories ✅ **Use repositories when:** - Complex queries with joins/includes - Query used in multiple places - Need caching layer - Want to mock for testing ❌ **Skip repositories for:** - Simple one-off queries - Prototyping (can refactor later) ### Repository Template ```typescript export class UserRepository { async findById(id: string): Promise { return PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); } async findActive(): Promise { return PrismaService.main.user.findMany({ where: { isActive: true }, orderBy: { createdAt: 'desc' }, }); } async create(data: Prisma.UserCreateInput): Promise { return PrismaService.main.user.create({ data }); } } ``` --- ## Transaction Patterns ### Simple Transaction ```typescript const result = await PrismaService.main.$transaction(async (tx) => { const user = await tx.user.create({ data: userData }); const profile = await tx.userProfile.create({ data: { userId: user.id } }); return { user, profile }; }); ``` ### Interactive Transaction ```typescript const result = await PrismaService.main.$transaction( async (tx) => { const user = await tx.user.findUnique({ where: { id } }); if (!user) throw new Error('User not found'); return await tx.user.update({ where: { id }, data: { lastLogin: new Date() }, }); }, { maxWait: 5000, timeout: 10000, } ); ``` --- ## Query Optimization ### Use select to Limit Fields ```typescript // ❌ Fetches all fields const users = await PrismaService.main.user.findMany(); // ✅ Only fetch needed fields const users = await PrismaService.main.user.findMany({ select: { id: true, email: true, profile: { select: { firstName: true, lastName: true } }, }, }); ``` ### Use include Carefully ```typescript // ❌ Excessive includes const user = await PrismaService.main.user.findUnique({ where: { id }, include: { profile: true, posts: { include: { comments: true } }, workflows: { include: { steps: { include: { actions: true } } } }, }, }); // ✅ Only include what you need const user = await PrismaService.main.user.findUnique({ where: { id }, include: { profile: true }, }); ``` --- ## N+1 Query Prevention ### Problem: N+1 Queries ```typescript // ❌ N+1 Query Problem const users = await PrismaService.main.user.findMany(); // 1 query for (const user of users) { // N queries (one per user) const profile = await PrismaService.main.userProfile.findUnique({ where: { userId: user.id }, }); } ``` ### Solution: Use include or Batching ```typescript // ✅ Single query with include const users = await PrismaService.main.user.findMany({ include: { profile: true }, }); // ✅ Or batch query const userIds = users.map(u => u.id); const profiles = await PrismaService.main.userProfile.findMany({ where: { userId: { in: userIds } }, }); ``` --- ## Error Handling ### Prisma Error Types ```typescript import { Prisma } from '@prisma/client'; try { await PrismaService.main.user.create({ data }); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError) { // Unique constraint violation if (error.code === 'P2002') { throw new ConflictError('Email already exists'); } // Foreign key constraint if (error.code === 'P2003') { throw new ValidationError('Invalid reference'); } // Record not found if (error.code === 'P2025') { throw new NotFoundError('Record not found'); } } // Unknown error Sentry.captureException(error); throw error; } ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [services-and-repositories.md](services-and-repositories.md) - [async-and-errors.md](async-and-errors.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/middleware-guide.md ================================================ # Middleware Guide - Express Middleware Patterns Complete guide to creating and using middleware in backend microservices. ## Table of Contents - [Authentication Middleware](#authentication-middleware) - [Audit Middleware with AsyncLocalStorage](#audit-middleware-with-asynclocalstorage) - [Error Boundary Middleware](#error-boundary-middleware) - [Validation Middleware](#validation-middleware) - [Composable Middleware](#composable-middleware) - [Middleware Ordering](#middleware-ordering) --- ## Authentication Middleware ### SSOMiddleware Pattern **File:** `/form/src/middleware/SSOMiddleware.ts` ```typescript export class SSOMiddlewareClient { static verifyLoginStatus(req: Request, res: Response, next: NextFunction): void { const token = req.cookies.refresh_token; if (!token) { return res.status(401).json({ error: 'Not authenticated' }); } try { const decoded = jwt.verify(token, config.tokens.jwt); res.locals.claims = decoded; res.locals.effectiveUserId = decoded.sub; next(); } catch (error) { res.status(401).json({ error: 'Invalid token' }); } } } ``` --- ## Audit Middleware with AsyncLocalStorage ### Excellent Pattern from Blog API **File:** `/form/src/middleware/auditMiddleware.ts` ```typescript import { AsyncLocalStorage } from 'async_hooks'; export interface AuditContext { userId: string; userName?: string; impersonatedBy?: string; sessionId?: string; timestamp: Date; requestId: string; } export const auditContextStorage = new AsyncLocalStorage(); export function auditMiddleware(req: Request, res: Response, next: NextFunction): void { const context: AuditContext = { userId: res.locals.effectiveUserId || 'anonymous', userName: res.locals.claims?.preferred_username, impersonatedBy: res.locals.isImpersonating ? res.locals.originalUserId : undefined, timestamp: new Date(), requestId: req.id || uuidv4(), }; auditContextStorage.run(context, () => { next(); }); } // Getter for current context export function getAuditContext(): AuditContext | null { return auditContextStorage.getStore() || null; } ``` **Benefits:** - Context propagates through entire request - No need to pass context through every function - Automatically available in services, repositories - Type-safe context access **Usage in Services:** ```typescript import { getAuditContext } from '../middleware/auditMiddleware'; async function someOperation() { const context = getAuditContext(); console.log('Operation by:', context?.userId); } ``` --- ## Error Boundary Middleware ### Comprehensive Error Handler **File:** `/form/src/middleware/errorBoundary.ts` ```typescript export function errorBoundary( error: Error, req: Request, res: Response, next: NextFunction ): void { // Determine status code const statusCode = getStatusCodeForError(error); // Capture to Sentry Sentry.withScope((scope) => { scope.setLevel(statusCode >= 500 ? 'error' : 'warning'); scope.setTag('error_type', error.name); scope.setContext('error_details', { message: error.message, stack: error.stack, }); Sentry.captureException(error); }); // User-friendly response res.status(statusCode).json({ success: false, error: { message: getUserFriendlyMessage(error), code: error.name, }, requestId: Sentry.getCurrentScope().getPropagationContext().traceId, }); } // Async wrapper export function asyncErrorWrapper( handler: (req: Request, res: Response, next: NextFunction) => Promise ) { return async (req: Request, res: Response, next: NextFunction) => { try { await handler(req, res, next); } catch (error) { next(error); } }; } ``` --- ## Composable Middleware ### withAuthAndAudit Pattern ```typescript export function withAuthAndAudit(...authMiddleware: any[]) { return [ ...authMiddleware, auditMiddleware, ]; } // Usage router.post('/:formID/submit', ...withAuthAndAudit(SSOMiddlewareClient.verifyLoginStatus), async (req, res) => controller.submit(req, res) ); ``` --- ## Middleware Ordering ### Critical Order (Must Follow) ```typescript // 1. Sentry request handler (FIRST) app.use(Sentry.Handlers.requestHandler()); // 2. Body parsing app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 3. Cookie parsing app.use(cookieParser()); // 4. Auth initialization app.use(SSOMiddleware.initialize()); // 5. Routes registered here app.use('/api/users', userRoutes); // 6. Error handler (AFTER routes) app.use(errorBoundary); // 7. Sentry error handler (LAST) app.use(Sentry.Handlers.errorHandler()); ``` **Rule:** Error handlers MUST be registered AFTER all routes! --- **Related Files:** - [SKILL.md](SKILL.md) - [routing-and-controllers.md](routing-and-controllers.md) - [async-and-errors.md](async-and-errors.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/routing-and-controllers.md ================================================ # Routing and Controllers - Best Practices Complete guide to clean route definitions and controller patterns. ## Table of Contents - [Routes: Routing Only](#routes-routing-only) - [BaseController Pattern](#basecontroller-pattern) - [Good Examples](#good-examples) - [Anti-Patterns](#anti-patterns) - [Refactoring Guide](#refactoring-guide) - [Error Handling](#error-handling) - [HTTP Status Codes](#http-status-codes) --- ## Routes: Routing Only ### The Golden Rule **Routes should ONLY:** - ✅ Define route paths - ✅ Register middleware - ✅ Delegate to controllers **Routes should NEVER:** - ❌ Contain business logic - ❌ Access database directly - ❌ Implement validation logic (use Zod + controller) - ❌ Format complex responses - ❌ Handle complex error scenarios ### Clean Route Pattern ```typescript // routes/userRoutes.ts import { Router } from 'express'; import { UserController } from '../controllers/UserController'; import { SSOMiddlewareClient } from '../middleware/SSOMiddleware'; import { auditMiddleware } from '../middleware/auditMiddleware'; const router = Router(); const controller = new UserController(); // ✅ CLEAN: Route definition only router.get('/:id', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.getUser(req, res) ); router.post('/', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.createUser(req, res) ); router.put('/:id', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.updateUser(req, res) ); export default router; ``` **Key Points:** - Each route: method, path, middleware chain, controller delegation - No try-catch needed (controller handles errors) - Clean, readable, maintainable - Easy to see all endpoints at a glance --- ## BaseController Pattern ### Why BaseController? **Benefits:** - Consistent error handling across all controllers - Automatic Sentry integration - Standardized response formats - Reusable helper methods - Performance tracking utilities - Logging and breadcrumb helpers ### BaseController Pattern (Template) **File:** `/email/src/controllers/BaseController.ts` ```typescript import * as Sentry from '@sentry/node'; import { Response } from 'express'; export abstract class BaseController { /** * Handle errors with Sentry integration */ protected handleError( error: unknown, res: Response, context: string, statusCode = 500 ): void { Sentry.withScope((scope) => { scope.setTag('controller', this.constructor.name); scope.setTag('operation', context); scope.setUser({ id: res.locals?.claims?.userId }); if (error instanceof Error) { scope.setContext('error_details', { message: error.message, stack: error.stack, }); } Sentry.captureException(error); }); res.status(statusCode).json({ success: false, error: { message: error instanceof Error ? error.message : 'An error occurred', code: statusCode, }, }); } /** * Handle success responses */ protected handleSuccess( res: Response, data: T, message?: string, statusCode = 200 ): void { res.status(statusCode).json({ success: true, message, data, }); } /** * Performance tracking wrapper */ protected async withTransaction( name: string, operation: string, callback: () => Promise ): Promise { return await Sentry.startSpan( { name, op: operation }, callback ); } /** * Validate required fields */ protected validateRequest( required: string[], actual: Record, res: Response ): boolean { const missing = required.filter((field) => !actual[field]); if (missing.length > 0) { Sentry.captureMessage( `Missing required fields: ${missing.join(', ')}`, 'warning' ); res.status(400).json({ success: false, error: { message: 'Missing required fields', code: 'VALIDATION_ERROR', details: { missing }, }, }); return false; } return true; } /** * Logging helpers */ protected logInfo(message: string, context?: Record): void { Sentry.addBreadcrumb({ category: this.constructor.name, message, level: 'info', data: context, }); } protected logWarning(message: string, context?: Record): void { Sentry.captureMessage(message, { level: 'warning', tags: { controller: this.constructor.name }, extra: context, }); } /** * Add Sentry breadcrumb */ protected addBreadcrumb( message: string, category: string, data?: Record ): void { Sentry.addBreadcrumb({ message, category, level: 'info', data }); } /** * Capture custom metric */ protected captureMetric(name: string, value: number, unit: string): void { Sentry.metrics.gauge(name, value, { unit }); } } ``` ### Using BaseController ```typescript // controllers/UserController.ts import { Request, Response } from 'express'; import { BaseController } from './BaseController'; import { UserService } from '../services/userService'; import { createUserSchema } from '../validators/userSchemas'; export class UserController extends BaseController { private userService: UserService; constructor() { super(); this.userService = new UserService(); } async getUser(req: Request, res: Response): Promise { try { this.addBreadcrumb('Fetching user', 'user_controller', { userId: req.params.id }); const user = await this.userService.findById(req.params.id); if (!user) { return this.handleError( new Error('User not found'), res, 'getUser', 404 ); } this.handleSuccess(res, user); } catch (error) { this.handleError(error, res, 'getUser'); } } async createUser(req: Request, res: Response): Promise { try { // Validate input const validated = createUserSchema.parse(req.body); // Track performance const user = await this.withTransaction( 'user.create', 'db.query', () => this.userService.create(validated) ); this.handleSuccess(res, user, 'User created successfully', 201); } catch (error) { this.handleError(error, res, 'createUser'); } } async updateUser(req: Request, res: Response): Promise { try { const validated = updateUserSchema.parse(req.body); const user = await this.userService.update(req.params.id, validated); this.handleSuccess(res, user, 'User updated'); } catch (error) { this.handleError(error, res, 'updateUser'); } } } ``` **Benefits:** - Consistent error handling - Automatic Sentry integration - Performance tracking - Clean, readable code - Easy to test --- ## Good Examples ### Example 1: Email Notification Routes (Excellent ✅) **File:** `/email/src/routes/notificationRoutes.ts` ```typescript import { Router } from 'express'; import { NotificationController } from '../controllers/NotificationController'; import { SSOMiddlewareClient } from '../middleware/SSOMiddleware'; const router = Router(); const controller = new NotificationController(); // ✅ EXCELLENT: Clean delegation router.get('/', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => controller.getNotifications(req, res) ); router.post('/', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => controller.createNotification(req, res) ); router.put('/:id/read', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => controller.markAsRead(req, res) ); export default router; ``` **What Makes This Excellent:** - Zero business logic in routes - Clear middleware chain - Consistent pattern - Easy to understand ### Example 2: Proxy Routes with Validation (Good ✅) **File:** `/form/src/routes/proxyRoutes.ts` ```typescript import { z } from 'zod'; const createProxySchema = z.object({ originalUserID: z.string().min(1), proxyUserID: z.string().min(1), startsAt: z.string().datetime(), expiresAt: z.string().datetime(), }); router.post('/', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => { try { const validated = createProxySchema.parse(req.body); const proxy = await proxyService.createProxyRelationship(validated); res.status(201).json({ success: true, data: proxy }); } catch (error) { handler.handleException(res, error); } } ); ``` **What Makes This Good:** - Zod validation - Delegates to service - Proper HTTP status codes - Error handling **Could Be Better:** - Move validation to controller - Use BaseController --- ## Anti-Patterns ### Anti-Pattern 1: Business Logic in Routes (Bad ❌) **File:** `/form/src/routes/responseRoutes.ts` (actual production code) ```typescript // ❌ ANTI-PATTERN: 200+ lines of business logic in route router.post('/:formID/submit', async (req: Request, res: Response) => { try { const username = res.locals.claims.preferred_username; const responses = req.body.responses; const stepInstanceId = req.body.stepInstanceId; // ❌ Permission checking in route const userId = await userProfileService.getProfileByEmail(username).then(p => p.id); const canComplete = await permissionService.canCompleteStep(userId, stepInstanceId); if (!canComplete) { return res.status(403).json({ error: 'No permission' }); } // ❌ Workflow logic in route const { createWorkflowEngine, CompleteStepCommand } = require('../workflow/core/WorkflowEngineV3'); const engine = await createWorkflowEngine(); const command = new CompleteStepCommand( stepInstanceId, userId, responses, additionalContext ); const events = await engine.executeCommand(command); // ❌ Impersonation handling in route if (res.locals.isImpersonating) { impersonationContextStore.storeContext(stepInstanceId, { originalUserId: res.locals.originalUserId, effectiveUserId: userId, }); } // ❌ Response processing in route const post = await PrismaService.main.post.findUnique({ where: { id: postData.id }, include: { comments: true }, }); // ❌ Permission check in route await checkPostPermissions(post, userId); // ... 100+ more lines of business logic res.json({ success: true, data: result }); } catch (e) { handler.handleException(res, e); } }); ``` **Why This Is Terrible:** - 200+ lines of business logic - Hard to test (requires HTTP mocking) - Hard to reuse (tied to route) - Mixed responsibilities - Difficult to debug - Performance tracking difficult ### How to Refactor (Step-by-Step) **Step 1: Create Controller** ```typescript // controllers/PostController.ts export class PostController extends BaseController { private postService: PostService; constructor() { super(); this.postService = new PostService(); } async createPost(req: Request, res: Response): Promise { try { const validated = createPostSchema.parse({ ...req.body, }); const result = await this.postService.createPost( validated, res.locals.userId ); this.handleSuccess(res, result, 'Post created successfully'); } catch (error) { this.handleError(error, res, 'createPost'); } } } ``` **Step 2: Create Service** ```typescript // services/postService.ts export class PostService { async createPost( data: CreatePostDTO, userId: string ): Promise { // Permission check const canCreate = await permissionService.canCreatePost(userId); if (!canCreate) { throw new ForbiddenError('No permission to create post'); } // Execute workflow const engine = await createWorkflowEngine(); const command = new CompleteStepCommand(/* ... */); const events = await engine.executeCommand(command); // Handle impersonation if needed if (context.isImpersonating) { await this.handleImpersonation(data.stepInstanceId, context); } // Synchronize roles await this.synchronizeRoles(events, userId); return { events, success: true }; } private async handleImpersonation(stepInstanceId: number, context: any) { impersonationContextStore.storeContext(stepInstanceId, { originalUserId: context.originalUserId, effectiveUserId: context.effectiveUserId, }); } private async synchronizeRoles(events: WorkflowEvent[], userId: string) { // Role synchronization logic } } ``` **Step 3: Update Route** ```typescript // routes/postRoutes.ts import { PostController } from '../controllers/PostController'; const router = Router(); const controller = new PostController(); // ✅ CLEAN: Just routing router.post('/', SSOMiddlewareClient.verifyLoginStatus, auditMiddleware, async (req, res) => controller.createPost(req, res) ); ``` **Result:** - Route: 8 lines (was 200+) - Controller: 25 lines (request handling) - Service: 50 lines (business logic) - Testable, reusable, maintainable! --- ## Error Handling ### Controller Error Handling ```typescript async createUser(req: Request, res: Response): Promise { try { const result = await this.userService.create(req.body); this.handleSuccess(res, result, 'User created', 201); } catch (error) { // BaseController.handleError automatically: // - Captures to Sentry with context // - Sets appropriate status code // - Returns formatted error response this.handleError(error, res, 'createUser'); } } ``` ### Custom Error Status Codes ```typescript async getUser(req: Request, res: Response): Promise { try { const user = await this.userService.findById(req.params.id); if (!user) { // Custom 404 status return this.handleError( new Error('User not found'), res, 'getUser', 404 // Custom status code ); } this.handleSuccess(res, user); } catch (error) { this.handleError(error, res, 'getUser'); } } ``` ### Validation Errors ```typescript async createUser(req: Request, res: Response): Promise { try { const validated = createUserSchema.parse(req.body); const user = await this.userService.create(validated); this.handleSuccess(res, user, 'User created', 201); } catch (error) { // Zod errors get 400 status if (error instanceof z.ZodError) { return this.handleError(error, res, 'createUser', 400); } this.handleError(error, res, 'createUser'); } } ``` --- ## HTTP Status Codes ### Standard Codes | Code | Use Case | Example | |------|----------|---------| | 200 | Success (GET, PUT) | User retrieved, Updated | | 201 | Created (POST) | User created | | 204 | No Content (DELETE) | User deleted | | 400 | Bad Request | Invalid input data | | 401 | Unauthorized | Not authenticated | | 403 | Forbidden | No permission | | 404 | Not Found | Resource doesn't exist | | 409 | Conflict | Duplicate resource | | 422 | Unprocessable Entity | Validation failed | | 500 | Internal Server Error | Unexpected error | ### Usage Examples ```typescript // 200 - Success (default) this.handleSuccess(res, user); // 201 - Created this.handleSuccess(res, user, 'Created', 201); // 400 - Bad Request this.handleError(error, res, 'operation', 400); // 404 - Not Found this.handleError(new Error('Not found'), res, 'operation', 404); // 403 - Forbidden this.handleError(new ForbiddenError('No permission'), res, 'operation', 403); ``` --- ## Refactoring Guide ### Identify Routes Needing Refactoring **Red Flags:** - Route file > 100 lines - Multiple try-catch blocks in one route - Direct database access (Prisma calls) - Complex business logic (if statements, loops) - Permission checks in routes **Check your routes:** ```bash # Find large route files wc -l form/src/routes/*.ts | sort -n # Find routes with Prisma usage grep -r "PrismaService" form/src/routes/ ``` ### Refactoring Process **1. Extract to Controller:** ```typescript // Before: Route with logic router.post('/action', async (req, res) => { try { // 50 lines of logic } catch (e) { handler.handleException(res, e); } }); // After: Clean route router.post('/action', (req, res) => controller.performAction(req, res)); // New controller method async performAction(req: Request, res: Response): Promise { try { const result = await this.service.performAction(req.body); this.handleSuccess(res, result); } catch (error) { this.handleError(error, res, 'performAction'); } } ``` **2. Extract to Service:** ```typescript // Controller stays thin async performAction(req: Request, res: Response): Promise { try { const validated = actionSchema.parse(req.body); const result = await this.actionService.execute(validated); this.handleSuccess(res, result); } catch (error) { this.handleError(error, res, 'performAction'); } } // Service contains business logic export class ActionService { async execute(data: ActionDTO): Promise { // All business logic here // Permission checks // Database operations // Complex transformations return result; } } ``` **3. Add Repository (if needed):** ```typescript // Service calls repository export class ActionService { constructor(private actionRepository: ActionRepository) {} async execute(data: ActionDTO): Promise { // Business logic const entity = await this.actionRepository.findById(data.id); // More logic return await this.actionRepository.update(data.id, changes); } } // Repository handles data access export class ActionRepository { async findById(id: number): Promise { return PrismaService.main.entity.findUnique({ where: { id } }); } async update(id: number, data: Partial): Promise { return PrismaService.main.entity.update({ where: { id }, data }); } } ``` --- **Related Files:** - [SKILL.md](SKILL.md) - Main guide - [services-and-repositories.md](services-and-repositories.md) - Service layer details - [complete-examples.md](complete-examples.md) - Full refactoring examples ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/sentry-and-monitoring.md ================================================ # Sentry Integration and Monitoring Complete guide to error tracking and performance monitoring with Sentry v8. ## Table of Contents - [Core Principles](#core-principles) - [Sentry Initialization](#sentry-initialization) - [Error Capture Patterns](#error-capture-patterns) - [Performance Monitoring](#performance-monitoring) - [Cron Job Monitoring](#cron-job-monitoring) - [Error Context Best Practices](#error-context-best-practices) - [Common Mistakes](#common-mistakes) --- ## Core Principles **MANDATORY**: All errors MUST be captured to Sentry. No exceptions. **ALL ERRORS MUST BE CAPTURED** - Use Sentry v8 with comprehensive error tracking across all services. --- ## Sentry Initialization ### instrument.ts Pattern **Location:** `src/instrument.ts` (MUST be first import in server.ts and all cron jobs) **Template for Microservices:** ```typescript import * as Sentry from '@sentry/node'; import * as fs from 'fs'; import * as path from 'path'; import * as ini from 'ini'; const sentryConfigPath = path.join(__dirname, '../sentry.ini'); const sentryConfig = ini.parse(fs.readFileSync(sentryConfigPath, 'utf-8')); Sentry.init({ dsn: sentryConfig.sentry?.dsn, environment: process.env.NODE_ENV || 'development', tracesSampleRate: parseFloat(sentryConfig.sentry?.tracesSampleRate || '0.1'), profilesSampleRate: parseFloat(sentryConfig.sentry?.profilesSampleRate || '0.1'), integrations: [ ...Sentry.getDefaultIntegrations({}), Sentry.extraErrorDataIntegration({ depth: 5 }), Sentry.localVariablesIntegration(), Sentry.requestDataIntegration({ include: { cookies: false, data: true, headers: true, ip: true, query_string: true, url: true, user: { id: true, email: true, username: true }, }, }), Sentry.consoleIntegration(), Sentry.contextLinesIntegration(), Sentry.prismaIntegration(), ], beforeSend(event, hint) { // Filter health checks if (event.request?.url?.includes('/healthcheck')) { return null; } // Scrub sensitive headers if (event.request?.headers) { delete event.request.headers['authorization']; delete event.request.headers['cookie']; } // Mask emails for PII if (event.user?.email) { event.user.email = event.user.email.replace(/^(.{2}).*(@.*)$/, '$1***$2'); } return event; }, ignoreErrors: [ /^Invalid JWT/, /^JWT expired/, 'NetworkError', ], }); // Set service context Sentry.setTags({ service: 'form', version: '1.0.1', }); Sentry.setContext('runtime', { node_version: process.version, platform: process.platform, }); ``` **Critical Points:** - PII protection built-in (beforeSend) - Filter non-critical errors - Comprehensive integrations - Prisma instrumentation - Service-specific tagging --- ## Error Capture Patterns ### 1. BaseController Pattern ```typescript // Use BaseController.handleError protected handleError(error: unknown, res: Response, context: string, statusCode = 500): void { Sentry.withScope((scope) => { scope.setTag('controller', this.constructor.name); scope.setTag('operation', context); scope.setUser({ id: res.locals?.claims?.userId }); Sentry.captureException(error); }); res.status(statusCode).json({ success: false, error: { message: error instanceof Error ? error.message : 'Error occurred' } }); } ``` ### 2. Workflow Error Handling ```typescript import { SentryHelper } from '../utils/sentryHelper'; try { await businessOperation(); } catch (error) { SentryHelper.captureOperationError(error, { operationType: 'POST_CREATION', entityId: 123, userId: 'user-123', operation: 'createPost', }); throw error; } ``` ### 3. Service Layer Error Handling ```typescript try { await someOperation(); } catch (error) { Sentry.captureException(error, { tags: { service: 'form', operation: 'someOperation' }, extra: { userId: currentUser.id, entityId: 123 } }); throw error; } ``` --- ## Performance Monitoring ### Database Performance Tracking ```typescript import { DatabasePerformanceMonitor } from '../utils/databasePerformance'; const result = await DatabasePerformanceMonitor.withPerformanceTracking( 'findMany', 'UserProfile', async () => { return await PrismaService.main.userProfile.findMany({ take: 5 }); } ); ``` ### API Endpoint Spans ```typescript router.post('/operation', async (req, res) => { return await Sentry.startSpan({ name: 'operation.execute', op: 'http.server', attributes: { 'http.method': 'POST', 'http.route': '/operation' } }, async () => { const result = await performOperation(); res.json(result); }); }); ``` --- ## Cron Job Monitoring ### Mandatory Pattern ```typescript #!/usr/bin/env node import '../instrument'; // FIRST LINE after shebang import * as Sentry from '@sentry/node'; async function main() { return await Sentry.startSpan({ name: 'cron.job-name', op: 'cron', attributes: { 'cron.job': 'job-name', 'cron.startTime': new Date().toISOString(), } }, async () => { try { // Cron job logic here } catch (error) { Sentry.captureException(error, { tags: { 'cron.job': 'job-name', 'error.type': 'execution_error' } }); console.error('[Cron] Error:', error); process.exit(1); } }); } main().then(() => { console.log('[Cron] Completed successfully'); process.exit(0); }).catch((error) => { console.error('[Cron] Fatal error:', error); process.exit(1); }); ``` --- ## Error Context Best Practices ### Rich Context Example ```typescript Sentry.withScope((scope) => { // User context scope.setUser({ id: user.id, email: user.email, username: user.username }); // Tags for filtering scope.setTag('service', 'form'); scope.setTag('endpoint', req.path); scope.setTag('method', req.method); // Structured context scope.setContext('operation', { type: 'workflow.complete', workflowId: 123, stepId: 456 }); // Breadcrumbs for timeline scope.addBreadcrumb({ category: 'workflow', message: 'Starting step completion', level: 'info', data: { stepId: 456 } }); Sentry.captureException(error); }); ``` --- ## Common Mistakes ```typescript // ❌ Swallowing errors try { await riskyOperation(); } catch (error) { // Silent failure } // ❌ Generic error messages throw new Error('Error occurred'); // ❌ Exposing sensitive data Sentry.captureException(error, { extra: { password: user.password } // NEVER }); // ❌ Missing async error handling async function bad() { fetchData().then(data => processResult(data)); // Unhandled } // ✅ Proper async handling async function good() { try { const data = await fetchData(); processResult(data); } catch (error) { Sentry.captureException(error); throw error; } } ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [routing-and-controllers.md](routing-and-controllers.md) - [async-and-errors.md](async-and-errors.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/services-and-repositories.md ================================================ # Services and Repositories - Business Logic Layer Complete guide to organizing business logic with services and data access with repositories. ## Table of Contents - [Service Layer Overview](#service-layer-overview) - [Dependency Injection Pattern](#dependency-injection-pattern) - [Singleton Pattern](#singleton-pattern) - [Repository Pattern](#repository-pattern) - [Service Design Principles](#service-design-principles) - [Caching Strategies](#caching-strategies) - [Testing Services](#testing-services) --- ## Service Layer Overview ### Purpose of Services **Services contain business logic** - the 'what' and 'why' of your application: ``` Controller asks: "Should I do this?" Service answers: "Yes/No, here's why, and here's what happens" Repository executes: "Here's the data you requested" ``` **Services are responsible for:** - ✅ Business rules enforcement - ✅ Orchestrating multiple repositories - ✅ Transaction management - ✅ Complex calculations - ✅ External service integration - ✅ Business validations **Services should NOT:** - ❌ Know about HTTP (Request/Response) - ❌ Direct Prisma access (use repositories) - ❌ Handle route-specific logic - ❌ Format HTTP responses --- ## Dependency Injection Pattern ### Why Dependency Injection? **Benefits:** - Easy to test (inject mocks) - Clear dependencies - Flexible configuration - Promotes loose coupling ### Excellent Example: NotificationService **File:** `/blog-api/src/services/NotificationService.ts` ```typescript // Define dependencies interface for clarity export interface NotificationServiceDependencies { prisma: PrismaClient; batchingService: BatchingService; emailComposer: EmailComposer; } // Service with dependency injection export class NotificationService { private prisma: PrismaClient; private batchingService: BatchingService; private emailComposer: EmailComposer; private preferencesCache: Map = new Map(); private CACHE_TTL = (notificationConfig.preferenceCacheTTLMinutes || 5) * 60 * 1000; // Dependencies injected via constructor constructor(dependencies: NotificationServiceDependencies) { this.prisma = dependencies.prisma; this.batchingService = dependencies.batchingService; this.emailComposer = dependencies.emailComposer; } /** * Create a notification and route it appropriately */ async createNotification(params: CreateNotificationParams) { const { recipientID, type, title, message, link, context = {}, channel = 'both', priority = NotificationPriority.NORMAL } = params; try { // Get template and render content const template = getNotificationTemplate(type); const rendered = renderNotificationContent(template, context); // Create in-app notification record const notificationId = await createNotificationRecord({ instanceId: parseInt(context.instanceId || '0', 10), template: type, recipientUserId: recipientID, channel: channel === 'email' ? 'email' : 'inApp', contextData: context, title: finalTitle, message: finalMessage, link: finalLink, }); // Route notification based on channel if (channel === 'email' || channel === 'both') { await this.routeNotification({ notificationId, userId: recipientID, type, priority, title: finalTitle, message: finalMessage, link: finalLink, context, }); } return notification; } catch (error) { ErrorLogger.log(error, { context: { '[NotificationService] createNotification': { type: params.type, recipientID: params.recipientID, }, }, }); throw error; } } /** * Route notification based on user preferences */ private async routeNotification(params: { notificationId: number; userId: string; type: string; priority: NotificationPriority; title: string; message: string; link?: string; context?: Record }) { // Get user preferences with caching const preferences = await this.getUserPreferences(params.userId); // Check if we should batch or send immediately if (this.shouldBatchEmail(preferences, params.type, params.priority)) { await this.batchingService.queueNotificationForBatch({ notificationId: params.notificationId, userId: params.userId, userPreference: preferences, priority: params.priority, }); } else { // Send immediately via EmailComposer await this.sendImmediateEmail({ userId: params.userId, title: params.title, message: params.message, link: params.link, context: params.context, type: params.type, }); } } /** * Determine if email should be batched */ shouldBatchEmail(preferences: UserPreference, notificationType: string, priority: NotificationPriority): boolean { // HIGH priority always immediate if (priority === NotificationPriority.HIGH) { return false; } // Check batch mode const batchMode = preferences.emailBatchMode || BatchMode.IMMEDIATE; return batchMode !== BatchMode.IMMEDIATE; } /** * Get user preferences with caching */ async getUserPreferences(userId: string): Promise { // Check cache first const cached = this.preferencesCache.get(userId); if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { return cached.preferences; } const preference = await this.prisma.userPreference.findUnique({ where: { userID: userId }, }); const finalPreferences = preference || DEFAULT_PREFERENCES; // Update cache this.preferencesCache.set(userId, { preferences: finalPreferences, timestamp: Date.now(), }); return finalPreferences; } } ``` **Usage in Controller:** ```typescript // Instantiate with dependencies const notificationService = new NotificationService({ prisma: PrismaService.main, batchingService: new BatchingService(PrismaService.main), emailComposer: new EmailComposer(), }); // Use in controller const notification = await notificationService.createNotification({ recipientID: 'user-123', type: 'AFRLWorkflowNotification', context: { workflowName: 'AFRL Monthly Report' }, }); ``` **Key Takeaways:** - Dependencies passed via constructor - Clear interface defines required dependencies - Easy to test (inject mocks) - Encapsulated caching logic - Business rules isolated from HTTP --- ## Singleton Pattern ### When to Use Singletons **Use for:** - Services with expensive initialization - Services with shared state (caching) - Services accessed from many places - Permission services - Configuration services ### Example: PermissionService (Singleton) **File:** `/blog-api/src/services/permissionService.ts` ```typescript import { PrismaClient } from '@prisma/client'; class PermissionService { private static instance: PermissionService; private prisma: PrismaClient; private permissionCache: Map = new Map(); private CACHE_TTL = 5 * 60 * 1000; // 5 minutes // Private constructor prevents direct instantiation private constructor() { this.prisma = PrismaService.main; } // Get singleton instance public static getInstance(): PermissionService { if (!PermissionService.instance) { PermissionService.instance = new PermissionService(); } return PermissionService.instance; } /** * Check if user can complete a workflow step */ async canCompleteStep(userId: string, stepInstanceId: number): Promise { const cacheKey = `${userId}:${stepInstanceId}`; // Check cache const cached = this.permissionCache.get(cacheKey); if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { return cached.canAccess; } try { const post = await this.prisma.post.findUnique({ where: { id: postId }, include: { author: true, comments: { include: { user: true, }, }, }, }); if (!post) { return false; } // Check if user has permission const canEdit = post.authorId === userId || await this.isUserAdmin(userId); // Cache result this.permissionCache.set(cacheKey, { canAccess: isAssigned, timestamp: Date.now(), }); return isAssigned; } catch (error) { console.error('[PermissionService] Error checking step permission:', error); return false; } } /** * Clear cache for user */ clearUserCache(userId: string): void { for (const [key] of this.permissionCache) { if (key.startsWith(`${userId}:`)) { this.permissionCache.delete(key); } } } /** * Clear all cache */ clearCache(): void { this.permissionCache.clear(); } } // Export singleton instance export const permissionService = PermissionService.getInstance(); ``` **Usage:** ```typescript import { permissionService } from '../services/permissionService'; // Use anywhere in the codebase const canComplete = await permissionService.canCompleteStep(userId, stepId); if (!canComplete) { throw new ForbiddenError('You do not have permission to complete this step'); } ``` --- ## Repository Pattern ### Purpose of Repositories **Repositories abstract data access** - the 'how' of data operations: ``` Service: "Get me all active users sorted by name" Repository: "Here's the Prisma query that does that" ``` **Repositories are responsible for:** - ✅ All Prisma operations - ✅ Query construction - ✅ Query optimization (select, include) - ✅ Database error handling - ✅ Caching database results **Repositories should NOT:** - ❌ Contain business logic - ❌ Know about HTTP - ❌ Make decisions (that's service layer) ### Repository Template ```typescript // repositories/UserRepository.ts import { PrismaService } from '@project-lifecycle-portal/database'; import type { User, Prisma } from '@project-lifecycle-portal/database'; export class UserRepository { /** * Find user by ID with optimized query */ async findById(userId: string): Promise { try { return await PrismaService.main.user.findUnique({ where: { userID: userId }, select: { userID: true, email: true, name: true, isActive: true, roles: true, createdAt: true, updatedAt: true, }, }); } catch (error) { console.error('[UserRepository] Error finding user by ID:', error); throw new Error(`Failed to find user: ${userId}`); } } /** * Find all active users */ async findActive(options?: { orderBy?: Prisma.UserOrderByWithRelationInput }): Promise { try { return await PrismaService.main.user.findMany({ where: { isActive: true }, orderBy: options?.orderBy || { name: 'asc' }, select: { userID: true, email: true, name: true, roles: true, }, }); } catch (error) { console.error('[UserRepository] Error finding active users:', error); throw new Error('Failed to find active users'); } } /** * Find user by email */ async findByEmail(email: string): Promise { try { return await PrismaService.main.user.findUnique({ where: { email }, }); } catch (error) { console.error('[UserRepository] Error finding user by email:', error); throw new Error(`Failed to find user with email: ${email}`); } } /** * Create new user */ async create(data: Prisma.UserCreateInput): Promise { try { return await PrismaService.main.user.create({ data }); } catch (error) { console.error('[UserRepository] Error creating user:', error); throw new Error('Failed to create user'); } } /** * Update user */ async update(userId: string, data: Prisma.UserUpdateInput): Promise { try { return await PrismaService.main.user.update({ where: { userID: userId }, data, }); } catch (error) { console.error('[UserRepository] Error updating user:', error); throw new Error(`Failed to update user: ${userId}`); } } /** * Delete user (soft delete by setting isActive = false) */ async delete(userId: string): Promise { try { return await PrismaService.main.user.update({ where: { userID: userId }, data: { isActive: false }, }); } catch (error) { console.error('[UserRepository] Error deleting user:', error); throw new Error(`Failed to delete user: ${userId}`); } } /** * Check if email exists */ async emailExists(email: string): Promise { try { const count = await PrismaService.main.user.count({ where: { email }, }); return count > 0; } catch (error) { console.error('[UserRepository] Error checking email exists:', error); throw new Error('Failed to check if email exists'); } } } // Export singleton instance export const userRepository = new UserRepository(); ``` **Using Repository in Service:** ```typescript // services/userService.ts import { userRepository } from '../repositories/UserRepository'; import { ConflictError, NotFoundError } from '../utils/errors'; export class UserService { /** * Create new user with business rules */ async createUser(data: { email: string; name: string; roles: string[] }): Promise { // Business rule: Check if email already exists const emailExists = await userRepository.emailExists(data.email); if (emailExists) { throw new ConflictError('Email already exists'); } // Business rule: Validate roles const validRoles = ['admin', 'operations', 'user']; const invalidRoles = data.roles.filter((role) => !validRoles.includes(role)); if (invalidRoles.length > 0) { throw new ValidationError(`Invalid roles: ${invalidRoles.join(', ')}`); } // Create user via repository return await userRepository.create({ email: data.email, name: data.name, roles: data.roles, isActive: true, }); } /** * Get user by ID */ async getUser(userId: string): Promise { const user = await userRepository.findById(userId); if (!user) { throw new NotFoundError(`User not found: ${userId}`); } return user; } } ``` --- ## Service Design Principles ### 1. Single Responsibility Each service should have ONE clear purpose: ```typescript // ✅ GOOD - Single responsibility class UserService { async createUser() {} async updateUser() {} async deleteUser() {} } class EmailService { async sendEmail() {} async sendBulkEmails() {} } // ❌ BAD - Too many responsibilities class UserService { async createUser() {} async sendWelcomeEmail() {} // Should be EmailService async logUserActivity() {} // Should be AuditService async processPayment() {} // Should be PaymentService } ``` ### 2. Clear Method Names Method names should describe WHAT they do: ```typescript // ✅ GOOD - Clear intent async createNotification() async getUserPreferences() async shouldBatchEmail() async routeNotification() // ❌ BAD - Vague or misleading async process() async handle() async doIt() async execute() ``` ### 3. Return Types Always use explicit return types: ```typescript // ✅ GOOD - Explicit types async createUser(data: CreateUserDTO): Promise {} async findUsers(): Promise {} async deleteUser(id: string): Promise {} // ❌ BAD - Implicit any async createUser(data) {} // No types! ``` ### 4. Error Handling Services should throw meaningful errors: ```typescript // ✅ GOOD - Meaningful errors if (!user) { throw new NotFoundError(`User not found: ${userId}`); } if (emailExists) { throw new ConflictError('Email already exists'); } // ❌ BAD - Generic errors if (!user) { throw new Error('Error'); // What error? } ``` ### 5. Avoid God Services Don't create services that do everything: ```typescript // ❌ BAD - God service class WorkflowService { async startWorkflow() {} async completeStep() {} async assignRoles() {} async sendNotifications() {} // Should be NotificationService async validatePermissions() {} // Should be PermissionService async logAuditTrail() {} // Should be AuditService // ... 50 more methods } // ✅ GOOD - Focused services class WorkflowService { constructor( private notificationService: NotificationService, private permissionService: PermissionService, private auditService: AuditService ) {} async startWorkflow() { // Orchestrate other services await this.permissionService.checkPermission(); await this.workflowRepository.create(); await this.notificationService.notify(); await this.auditService.log(); } } ``` --- ## Caching Strategies ### 1. In-Memory Caching ```typescript class UserService { private cache: Map = new Map(); private CACHE_TTL = 5 * 60 * 1000; // 5 minutes async getUser(userId: string): Promise { // Check cache const cached = this.cache.get(userId); if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) { return cached.user; } // Fetch from database const user = await userRepository.findById(userId); // Update cache if (user) { this.cache.set(userId, { user, timestamp: Date.now() }); } return user; } clearUserCache(userId: string): void { this.cache.delete(userId); } } ``` ### 2. Cache Invalidation ```typescript class UserService { async updateUser(userId: string, data: UpdateUserDTO): Promise { // Update in database const user = await userRepository.update(userId, data); // Invalidate cache this.clearUserCache(userId); return user; } } ``` --- ## Testing Services ### Unit Tests ```typescript // tests/userService.test.ts import { UserService } from '../services/userService'; import { userRepository } from '../repositories/UserRepository'; import { ConflictError } from '../utils/errors'; // Mock repository jest.mock('../repositories/UserRepository'); describe('UserService', () => { let userService: UserService; beforeEach(() => { userService = new UserService(); jest.clearAllMocks(); }); describe('createUser', () => { it('should create user when email does not exist', async () => { // Arrange const userData = { email: 'test@example.com', name: 'Test User', roles: ['user'], }; (userRepository.emailExists as jest.Mock).mockResolvedValue(false); (userRepository.create as jest.Mock).mockResolvedValue({ userID: '123', ...userData, }); // Act const user = await userService.createUser(userData); // Assert expect(user).toBeDefined(); expect(user.email).toBe(userData.email); expect(userRepository.emailExists).toHaveBeenCalledWith(userData.email); expect(userRepository.create).toHaveBeenCalled(); }); it('should throw ConflictError when email exists', async () => { // Arrange const userData = { email: 'existing@example.com', name: 'Test User', roles: ['user'], }; (userRepository.emailExists as jest.Mock).mockResolvedValue(true); // Act & Assert await expect(userService.createUser(userData)).rejects.toThrow(ConflictError); expect(userRepository.create).not.toHaveBeenCalled(); }); }); }); ``` --- **Related Files:** - [SKILL.md](SKILL.md) - Main guide - [routing-and-controllers.md](routing-and-controllers.md) - Controllers that use services - [database-patterns.md](database-patterns.md) - Prisma and repository patterns - [complete-examples.md](complete-examples.md) - Full service/repository examples ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/testing-guide.md ================================================ # Testing Guide - Backend Testing Strategies Complete guide to testing backend services with Jest and best practices. ## Table of Contents - [Unit Testing](#unit-testing) - [Integration Testing](#integration-testing) - [Mocking Strategies](#mocking-strategies) - [Test Data Management](#test-data-management) - [Testing Authenticated Routes](#testing-authenticated-routes) - [Coverage Targets](#coverage-targets) --- ## Unit Testing ### Test Structure ```typescript // services/userService.test.ts import { UserService } from './userService'; import { UserRepository } from '../repositories/UserRepository'; jest.mock('../repositories/UserRepository'); describe('UserService', () => { let service: UserService; let mockRepository: jest.Mocked; beforeEach(() => { mockRepository = { findByEmail: jest.fn(), create: jest.fn(), } as any; service = new UserService(); (service as any).userRepository = mockRepository; }); afterEach(() => { jest.clearAllMocks(); }); describe('create', () => { it('should throw error if email exists', async () => { mockRepository.findByEmail.mockResolvedValue({ id: '123' } as any); await expect( service.create({ email: 'test@test.com' }) ).rejects.toThrow('Email already in use'); }); it('should create user if email is unique', async () => { mockRepository.findByEmail.mockResolvedValue(null); mockRepository.create.mockResolvedValue({ id: '123' } as any); const user = await service.create({ email: 'test@test.com', firstName: 'John', lastName: 'Doe', }); expect(user).toBeDefined(); expect(mockRepository.create).toHaveBeenCalledWith( expect.objectContaining({ email: 'test@test.com' }) ); }); }); }); ``` --- ## Integration Testing ### Test with Real Database ```typescript import { PrismaService } from '@project-lifecycle-portal/database'; describe('UserService Integration', () => { let testUser: any; beforeAll(async () => { // Create test data testUser = await PrismaService.main.user.create({ data: { email: 'test@test.com', profile: { create: { firstName: 'Test', lastName: 'User' } }, }, }); }); afterAll(async () => { // Cleanup await PrismaService.main.user.delete({ where: { id: testUser.id } }); }); it('should find user by email', async () => { const user = await userService.findByEmail('test@test.com'); expect(user).toBeDefined(); expect(user?.email).toBe('test@test.com'); }); }); ``` --- ## Mocking Strategies ### Mock PrismaService ```typescript jest.mock('@project-lifecycle-portal/database', () => ({ PrismaService: { main: { user: { findMany: jest.fn(), findUnique: jest.fn(), create: jest.fn(), update: jest.fn(), }, }, isAvailable: true, }, })); ``` ### Mock Services ```typescript const mockUserService = { findById: jest.fn(), create: jest.fn(), update: jest.fn(), } as jest.Mocked; ``` --- ## Test Data Management ### Setup and Teardown ```typescript describe('PermissionService', () => { let instanceId: number; beforeAll(async () => { // Create test post const post = await PrismaService.main.post.create({ data: { title: 'Test Post', content: 'Test', authorId: 'test-user' }, }); instanceId = post.id; }); afterAll(async () => { // Cleanup await PrismaService.main.post.delete({ where: { id: instanceId }, }); }); beforeEach(() => { // Clear caches permissionService.clearCache(); }); it('should check permissions', async () => { const hasPermission = await permissionService.checkPermission( 'user-id', instanceId, 'VIEW_WORKFLOW' ); expect(hasPermission).toBeDefined(); }); }); ``` --- ## Testing Authenticated Routes ### Using test-auth-route.js ```bash # Test authenticated endpoint node scripts/test-auth-route.js http://localhost:3002/form/api/users # Test with POST data node scripts/test-auth-route.js http://localhost:3002/form/api/users POST '{"email":"test@test.com"}' ``` ### Mock Authentication in Tests ```typescript // Mock auth middleware jest.mock('../middleware/SSOMiddleware', () => ({ SSOMiddlewareClient: { verifyLoginStatus: (req, res, next) => { res.locals.claims = { sub: 'test-user-id', preferred_username: 'testuser', }; next(); }, }, })); ``` --- ## Coverage Targets ### Recommended Coverage - **Unit Tests**: 70%+ coverage - **Integration Tests**: Critical paths covered - **E2E Tests**: Happy paths covered ### Run Coverage ```bash npm test -- --coverage ``` --- **Related Files:** - [SKILL.md](SKILL.md) - [services-and-repositories.md](services-and-repositories.md) - [complete-examples.md](complete-examples.md) ================================================ FILE: .claude/skills/backend-dev-guidelines/resources/validation-patterns.md ================================================ # Validation Patterns - Input Validation with Zod Complete guide to input validation using Zod schemas for type-safe validation. ## Table of Contents - [Why Zod?](#why-zod) - [Basic Zod Patterns](#basic-zod-patterns) - [Schema Examples from Codebase](#schema-examples-from-codebase) - [Route-Level Validation](#route-level-validation) - [Controller Validation](#controller-validation) - [DTO Pattern](#dto-pattern) - [Error Handling](#error-handling) - [Advanced Patterns](#advanced-patterns) --- ## Why Zod? ### Benefits Over Joi/Other Libraries **Type Safety:** - ✅ Full TypeScript inference - ✅ Runtime + compile-time validation - ✅ Automatic type generation **Developer Experience:** - ✅ Intuitive API - ✅ Composable schemas - ✅ Excellent error messages **Performance:** - ✅ Fast validation - ✅ Small bundle size - ✅ Tree-shakeable ### Migration from Joi Modern validation uses Zod instead of Joi: ```typescript // ❌ OLD - Joi (being phased out) const schema = Joi.object({ email: Joi.string().email().required(), name: Joi.string().min(3).required(), }); // ✅ NEW - Zod (preferred) const schema = z.object({ email: z.string().email(), name: z.string().min(3), }); ``` --- ## Basic Zod Patterns ### Primitive Types ```typescript import { z } from 'zod'; // Strings const nameSchema = z.string(); const emailSchema = z.string().email(); const urlSchema = z.string().url(); const uuidSchema = z.string().uuid(); const minLengthSchema = z.string().min(3); const maxLengthSchema = z.string().max(100); // Numbers const ageSchema = z.number().int().positive(); const priceSchema = z.number().positive(); const rangeSchema = z.number().min(0).max(100); // Booleans const activeSchema = z.boolean(); // Dates const dateSchema = z.string().datetime(); // ISO 8601 string const nativeDateSchema = z.date(); // Native Date object // Enums const roleSchema = z.enum(['admin', 'operations', 'user']); const statusSchema = z.enum(['PENDING', 'APPROVED', 'REJECTED']); ``` ### Objects ```typescript // Simple object const userSchema = z.object({ email: z.string().email(), name: z.string(), age: z.number().int().positive(), }); // Nested objects const addressSchema = z.object({ street: z.string(), city: z.string(), zipCode: z.string().regex(/^\d{5}$/), }); const userWithAddressSchema = z.object({ name: z.string(), address: addressSchema, }); // Optional fields const userSchema = z.object({ name: z.string(), email: z.string().email().optional(), phone: z.string().optional(), }); // Nullable fields const userSchema = z.object({ name: z.string(), middleName: z.string().nullable(), }); ``` ### Arrays ```typescript // Array of primitives const rolesSchema = z.array(z.string()); const numbersSchema = z.array(z.number()); // Array of objects const usersSchema = z.array( z.object({ id: z.string(), name: z.string(), }) ); // Array with constraints const tagsSchema = z.array(z.string()).min(1).max(10); const nonEmptyArray = z.array(z.string()).nonempty(); ``` --- ## Schema Examples from Codebase ### Form Validation Schemas **File:** `/form/src/helpers/zodSchemas.ts` ```typescript import { z } from 'zod'; // Question types enum export const questionTypeSchema = z.enum([ 'input', 'textbox', 'editor', 'dropdown', 'autocomplete', 'checkbox', 'radio', 'upload', ]); // Upload types export const uploadTypeSchema = z.array( z.enum(['pdf', 'image', 'excel', 'video', 'powerpoint', 'word']).nullable() ); // Input types export const inputTypeSchema = z .enum(['date', 'number', 'input', 'currency']) .nullable(); // Question option export const questionOptionSchema = z.object({ id: z.number().int().positive().optional(), controlTag: z.string().max(150).nullable().optional(), label: z.string().max(100).nullable().optional(), order: z.number().int().min(0).default(0), }); // Question schema export const questionSchema = z.object({ id: z.number().int().positive().optional(), formID: z.number().int().positive(), sectionID: z.number().int().positive().optional(), options: z.array(questionOptionSchema).optional(), label: z.string().max(500), description: z.string().max(5000).optional(), type: questionTypeSchema, uploadTypes: uploadTypeSchema.optional(), inputType: inputTypeSchema.optional(), tags: z.array(z.string().max(150)).optional(), required: z.boolean(), isStandard: z.boolean().optional(), deprecatedKey: z.string().nullable().optional(), maxLength: z.number().int().positive().nullable().optional(), isOptionsSorted: z.boolean().optional(), }); // Form section schema export const formSectionSchema = z.object({ id: z.number().int().positive(), formID: z.number().int().positive(), questions: z.array(questionSchema).optional(), label: z.string().max(500), description: z.string().max(5000).optional(), isStandard: z.boolean(), }); // Create form schema export const createFormSchema = z.object({ id: z.number().int().positive(), label: z.string().max(150), description: z.string().max(6000).nullable().optional(), isPhase: z.boolean().optional(), username: z.string(), }); // Update order schema export const updateOrderSchema = z.object({ source: z.object({ index: z.number().int().min(0), sectionID: z.number().int().min(0), }), destination: z.object({ index: z.number().int().min(0), sectionID: z.number().int().min(0), }), }); // Controller-specific validation schemas export const createQuestionValidationSchema = z.object({ formID: z.number().int().positive(), sectionID: z.number().int().positive(), question: questionSchema, index: z.number().int().min(0).nullable().optional(), username: z.string(), }); export const updateQuestionValidationSchema = z.object({ questionID: z.number().int().positive(), username: z.string(), question: questionSchema, }); ``` ### Proxy Relationship Schema ```typescript // Proxy relationship validation const createProxySchema = z.object({ originalUserID: z.string().min(1), proxyUserID: z.string().min(1), startsAt: z.string().datetime(), expiresAt: z.string().datetime(), }); // With custom validation const createProxySchemaWithValidation = createProxySchema.refine( (data) => new Date(data.expiresAt) > new Date(data.startsAt), { message: 'expiresAt must be after startsAt', path: ['expiresAt'], } ); ``` ### Workflow Validation ```typescript // Workflow start schema const startWorkflowSchema = z.object({ workflowCode: z.string().min(1), entityType: z.enum(['Post', 'User', 'Comment']), entityID: z.number().int().positive(), dryRun: z.boolean().optional().default(false), }); // Workflow step completion schema const completeStepSchema = z.object({ stepInstanceID: z.number().int().positive(), answers: z.record(z.string(), z.any()), dryRun: z.boolean().optional().default(false), }); ``` --- ## Route-Level Validation ### Pattern 1: Inline Validation ```typescript // routes/proxyRoutes.ts import { z } from 'zod'; const createProxySchema = z.object({ originalUserID: z.string().min(1), proxyUserID: z.string().min(1), startsAt: z.string().datetime(), expiresAt: z.string().datetime(), }); router.post( '/', SSOMiddlewareClient.verifyLoginStatus, async (req, res) => { try { // Validate at route level const validated = createProxySchema.parse(req.body); // Delegate to service const proxy = await proxyService.createProxyRelationship(validated); res.status(201).json({ success: true, data: proxy }); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ success: false, error: { message: 'Validation failed', details: error.errors, }, }); } handler.handleException(res, error); } } ); ``` **Pros:** - Quick and simple - Good for simple routes **Cons:** - Validation logic in routes - Harder to test - Not reusable --- ## Controller Validation ### Pattern 2: Controller Validation (Recommended) ```typescript // validators/userSchemas.ts import { z } from 'zod'; export const createUserSchema = z.object({ email: z.string().email(), name: z.string().min(2).max(100), roles: z.array(z.enum(['admin', 'operations', 'user'])), isActive: z.boolean().default(true), }); export const updateUserSchema = z.object({ email: z.string().email().optional(), name: z.string().min(2).max(100).optional(), roles: z.array(z.enum(['admin', 'operations', 'user'])).optional(), isActive: z.boolean().optional(), }); export type CreateUserDTO = z.infer; export type UpdateUserDTO = z.infer; ``` ```typescript // controllers/UserController.ts import { Request, Response } from 'express'; import { BaseController } from './BaseController'; import { UserService } from '../services/userService'; import { createUserSchema, updateUserSchema } from '../validators/userSchemas'; import { z } from 'zod'; export class UserController extends BaseController { private userService: UserService; constructor() { super(); this.userService = new UserService(); } async createUser(req: Request, res: Response): Promise { try { // Validate input const validated = createUserSchema.parse(req.body); // Call service const user = await this.userService.createUser(validated); this.handleSuccess(res, user, 'User created successfully', 201); } catch (error) { if (error instanceof z.ZodError) { // Handle validation errors with 400 status return this.handleError(error, res, 'createUser', 400); } this.handleError(error, res, 'createUser'); } } async updateUser(req: Request, res: Response): Promise { try { // Validate params and body const userId = req.params.id; const validated = updateUserSchema.parse(req.body); const user = await this.userService.updateUser(userId, validated); this.handleSuccess(res, user, 'User updated successfully'); } catch (error) { if (error instanceof z.ZodError) { return this.handleError(error, res, 'updateUser', 400); } this.handleError(error, res, 'updateUser'); } } } ``` **Pros:** - Clean separation - Reusable schemas - Easy to test - Type-safe DTOs **Cons:** - More files to manage --- ## DTO Pattern ### Type Inference from Schemas ```typescript import { z } from 'zod'; // Define schema const createUserSchema = z.object({ email: z.string().email(), name: z.string(), age: z.number().int().positive(), }); // Infer TypeScript type from schema type CreateUserDTO = z.infer; // Equivalent to: // type CreateUserDTO = { // email: string; // name: string; // age: number; // } // Use in service class UserService { async createUser(data: CreateUserDTO): Promise { // data is fully typed! console.log(data.email); // ✅ TypeScript knows this exists console.log(data.invalid); // ❌ TypeScript error! } } ``` ### Input vs Output Types ```typescript // Input schema (what API receives) const createUserInputSchema = z.object({ email: z.string().email(), name: z.string(), password: z.string().min(8), }); // Output schema (what API returns) const userOutputSchema = z.object({ id: z.string().uuid(), email: z.string().email(), name: z.string(), createdAt: z.string().datetime(), // password excluded! }); type CreateUserInput = z.infer; type UserOutput = z.infer; ``` --- ## Error Handling ### Zod Error Format ```typescript try { const validated = schema.parse(data); } catch (error) { if (error instanceof z.ZodError) { console.log(error.errors); // [ // { // code: 'invalid_type', // expected: 'string', // received: 'number', // path: ['email'], // message: 'Expected string, received number' // } // ] } } ``` ### Custom Error Messages ```typescript const userSchema = z.object({ email: z.string().email({ message: 'Please provide a valid email address' }), name: z.string().min(2, { message: 'Name must be at least 2 characters' }), age: z.number().int().positive({ message: 'Age must be a positive number' }), }); ``` ### Formatted Error Response ```typescript // Helper function to format Zod errors function formatZodError(error: z.ZodError) { return { message: 'Validation failed', errors: error.errors.map((err) => ({ field: err.path.join('.'), message: err.message, code: err.code, })), }; } // In controller catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ success: false, error: formatZodError(error), }); } } // Response example: // { // "success": false, // "error": { // "message": "Validation failed", // "errors": [ // { // "field": "email", // "message": "Invalid email", // "code": "invalid_string" // } // ] // } // } ``` --- ## Advanced Patterns ### Conditional Validation ```typescript // Validate based on other field values const submissionSchema = z.object({ type: z.enum(['NEW', 'UPDATE']), postId: z.number().optional(), }).refine( (data) => { // If type is UPDATE, postId is required if (data.type === 'UPDATE') { return data.postId !== undefined; } return true; }, { message: 'postId is required when type is UPDATE', path: ['postId'], } ); ``` ### Transform Data ```typescript // Transform strings to numbers const userSchema = z.object({ name: z.string(), age: z.string().transform((val) => parseInt(val, 10)), }); // Transform dates const eventSchema = z.object({ name: z.string(), date: z.string().transform((str) => new Date(str)), }); ``` ### Preprocess Data ```typescript // Trim strings before validation const userSchema = z.object({ email: z.preprocess( (val) => typeof val === 'string' ? val.trim().toLowerCase() : val, z.string().email() ), name: z.preprocess( (val) => typeof val === 'string' ? val.trim() : val, z.string().min(2) ), }); ``` ### Union Types ```typescript // Multiple possible types const idSchema = z.union([z.string(), z.number()]); // Discriminated unions const notificationSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('email'), recipient: z.string().email(), subject: z.string(), }), z.object({ type: z.literal('sms'), phoneNumber: z.string(), message: z.string(), }), ]); ``` ### Recursive Schemas ```typescript // For nested structures like trees type Category = { id: number; name: string; children?: Category[]; }; const categorySchema: z.ZodType = z.lazy(() => z.object({ id: z.number(), name: z.string(), children: z.array(categorySchema).optional(), }) ); ``` ### Schema Composition ```typescript // Base schemas const timestampsSchema = z.object({ createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); const auditSchema = z.object({ createdBy: z.string(), updatedBy: z.string(), }); // Compose schemas const userSchema = z.object({ id: z.string(), email: z.string().email(), name: z.string(), }).merge(timestampsSchema).merge(auditSchema); // Extend schemas const adminUserSchema = userSchema.extend({ adminLevel: z.number().int().min(1).max(5), permissions: z.array(z.string()), }); // Pick specific fields const publicUserSchema = userSchema.pick({ id: true, name: true, // email excluded }); // Omit fields const userWithoutTimestamps = userSchema.omit({ createdAt: true, updatedAt: true, }); ``` ### Validation Middleware ```typescript // Create reusable validation middleware import { Request, Response, NextFunction } from 'express'; import { z } from 'zod'; export function validateBody(schema: T) { return (req: Request, res: Response, next: NextFunction) => { try { req.body = schema.parse(req.body); next(); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ success: false, error: { message: 'Validation failed', details: error.errors, }, }); } next(error); } }; } // Usage router.post('/users', validateBody(createUserSchema), async (req, res) => { // req.body is validated and typed! const user = await userService.createUser(req.body); res.json({ success: true, data: user }); } ); ``` --- **Related Files:** - [SKILL.md](SKILL.md) - Main guide - [routing-and-controllers.md](routing-and-controllers.md) - Using validation in controllers - [services-and-repositories.md](services-and-repositories.md) - Using DTOs in services - [async-and-errors.md](async-and-errors.md) - Error handling patterns ================================================ FILE: .claude/skills/error-tracking/SKILL.md ================================================ --- name: error-tracking description: Add Sentry v8 error tracking and performance monitoring to your project services. Use this skill when adding error handling, creating new controllers, instrumenting cron jobs, or tracking database performance. ALL ERRORS MUST BE CAPTURED TO SENTRY - no exceptions. --- # your project Sentry Integration Skill ## Purpose This skill enforces comprehensive Sentry error tracking and performance monitoring across all your project services following Sentry v8 patterns. ## When to Use This Skill - Adding error handling to any code - Creating new controllers or routes - Instrumenting cron jobs - Tracking database performance - Adding performance spans - Handling workflow errors ## 🚨 CRITICAL RULE **ALL ERRORS MUST BE CAPTURED TO SENTRY** - No exceptions. Never use console.error alone. ## Current Status ### Form Service ✅ Complete - Sentry v8 fully integrated - All workflow errors tracked - SystemActionQueueProcessor instrumented - Test endpoints available ### Email Service 🟡 In Progress - Phase 1-2 complete (6/22 tasks) - 189 ErrorLogger.log() calls remaining ## Sentry Integration Patterns ### 1. Controller Error Handling ```typescript // ✅ CORRECT - Use BaseController import { BaseController } from '../controllers/BaseController'; export class MyController extends BaseController { async myMethod() { try { // ... your code } catch (error) { this.handleError(error, 'myMethod'); // Automatically sends to Sentry } } } ``` ### 2. Route Error Handling (Without BaseController) ```typescript import * as Sentry from '@sentry/node'; router.get('/route', async (req, res) => { try { // ... your code } catch (error) { Sentry.captureException(error, { tags: { route: '/route', method: 'GET' }, extra: { userId: req.user?.id } }); res.status(500).json({ error: 'Internal server error' }); } }); ``` ### 3. Workflow Error Handling ```typescript import { WorkflowSentryHelper } from '../workflow/utils/sentryHelper'; // ✅ CORRECT - Use WorkflowSentryHelper WorkflowSentryHelper.captureWorkflowError(error, { workflowCode: 'DHS_CLOSEOUT', instanceId: 123, stepId: 456, userId: 'user-123', operation: 'stepCompletion', metadata: { additionalInfo: 'value' } }); ``` ### 4. Cron Jobs (MANDATORY Pattern) ```typescript #!/usr/bin/env node // FIRST LINE after shebang - CRITICAL! import '../instrument'; import * as Sentry from '@sentry/node'; async function main() { return await Sentry.startSpan({ name: 'cron.job-name', op: 'cron', attributes: { 'cron.job': 'job-name', 'cron.startTime': new Date().toISOString(), } }, async () => { try { // Your cron job logic } catch (error) { Sentry.captureException(error, { tags: { 'cron.job': 'job-name', 'error.type': 'execution_error' } }); console.error('[Job] Error:', error); process.exit(1); } }); } main() .then(() => { console.log('[Job] Completed successfully'); process.exit(0); }) .catch((error) => { console.error('[Job] Fatal error:', error); process.exit(1); }); ``` ### 5. Database Performance Monitoring ```typescript import { DatabasePerformanceMonitor } from '../utils/databasePerformance'; // ✅ CORRECT - Wrap database operations const result = await DatabasePerformanceMonitor.withPerformanceTracking( 'findMany', 'UserProfile', async () => { return await PrismaService.main.userProfile.findMany({ take: 5, }); } ); ``` ### 6. Async Operations with Spans ```typescript import * as Sentry from '@sentry/node'; const result = await Sentry.startSpan({ name: 'operation.name', op: 'operation.type', attributes: { 'custom.attribute': 'value' } }, async () => { // Your async operation return await someAsyncOperation(); }); ``` ## Error Levels Use appropriate severity levels: - **fatal**: System is unusable (database down, critical service failure) - **error**: Operation failed, needs immediate attention - **warning**: Recoverable issues, degraded performance - **info**: Informational messages, successful operations - **debug**: Detailed debugging information (dev only) ## Required Context ```typescript import * as Sentry from '@sentry/node'; Sentry.withScope((scope) => { // ALWAYS include these if available scope.setUser({ id: userId }); scope.setTag('service', 'form'); // or 'email', 'users', etc. scope.setTag('environment', process.env.NODE_ENV); // Add operation-specific context scope.setContext('operation', { type: 'workflow.start', workflowCode: 'DHS_CLOSEOUT', entityId: 123 }); Sentry.captureException(error); }); ``` ## Service-Specific Integration ### Form Service **Location**: `./blog-api/src/instrument.ts` ```typescript import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || 'development', integrations: [ nodeProfilingIntegration(), ], tracesSampleRate: 0.1, profilesSampleRate: 0.1, }); ``` **Key Helpers**: - `WorkflowSentryHelper` - Workflow-specific errors - `DatabasePerformanceMonitor` - DB query tracking - `BaseController` - Controller error handling ### Email Service **Location**: `./notifications/src/instrument.ts` ```typescript import * as Sentry from '@sentry/node'; import { nodeProfilingIntegration } from '@sentry/profiling-node'; Sentry.init({ dsn: process.env.SENTRY_DSN, environment: process.env.NODE_ENV || 'development', integrations: [ nodeProfilingIntegration(), ], tracesSampleRate: 0.1, profilesSampleRate: 0.1, }); ``` **Key Helpers**: - `EmailSentryHelper` - Email-specific errors - `BaseController` - Controller error handling ## Configuration (config.ini) ```ini [sentry] dsn = your-sentry-dsn environment = development tracesSampleRate = 0.1 profilesSampleRate = 0.1 [databaseMonitoring] enableDbTracing = true slowQueryThreshold = 100 logDbQueries = false dbErrorCapture = true enableN1Detection = true ``` ## Testing Sentry Integration ### Form Service Test Endpoints ```bash # Test basic error capture curl http://localhost:3002/blog-api/api/sentry/test-error # Test workflow error curl http://localhost:3002/blog-api/api/sentry/test-workflow-error # Test database performance curl http://localhost:3002/blog-api/api/sentry/test-database-performance # Test error boundary curl http://localhost:3002/blog-api/api/sentry/test-error-boundary ``` ### Email Service Test Endpoints ```bash # Test basic error capture curl http://localhost:3003/notifications/api/sentry/test-error # Test email-specific error curl http://localhost:3003/notifications/api/sentry/test-email-error # Test performance tracking curl http://localhost:3003/notifications/api/sentry/test-performance ``` ## Performance Monitoring ### Requirements 1. **All API endpoints** must have transaction tracking 2. **Database queries > 100ms** are automatically flagged 3. **N+1 queries** are detected and reported 4. **Cron jobs** must track execution time ### Transaction Tracking ```typescript import * as Sentry from '@sentry/node'; // Automatic transaction tracking for Express routes app.use(Sentry.Handlers.requestHandler()); app.use(Sentry.Handlers.tracingHandler()); // Manual transaction for custom operations const transaction = Sentry.startTransaction({ op: 'operation.type', name: 'Operation Name', }); try { // Your operation } finally { transaction.finish(); } ``` ## Common Mistakes to Avoid ❌ **NEVER** use console.error without Sentry ❌ **NEVER** swallow errors silently ❌ **NEVER** expose sensitive data in error context ❌ **NEVER** use generic error messages without context ❌ **NEVER** skip error handling in async operations ❌ **NEVER** forget to import instrument.ts as first line in cron jobs ## Implementation Checklist When adding Sentry to new code: - [ ] Imported Sentry or appropriate helper - [ ] All try/catch blocks capture to Sentry - [ ] Added meaningful context to errors - [ ] Used appropriate error level - [ ] No sensitive data in error messages - [ ] Added performance tracking for slow operations - [ ] Tested error handling paths - [ ] For cron jobs: instrument.ts imported first ## Key Files ### Form Service - `/blog-api/src/instrument.ts` - Sentry initialization - `/blog-api/src/workflow/utils/sentryHelper.ts` - Workflow errors - `/blog-api/src/utils/databasePerformance.ts` - DB monitoring - `/blog-api/src/controllers/BaseController.ts` - Controller base ### Email Service - `/notifications/src/instrument.ts` - Sentry initialization - `/notifications/src/utils/EmailSentryHelper.ts` - Email errors - `/notifications/src/controllers/BaseController.ts` - Controller base ### Configuration - `/blog-api/config.ini` - Form service config - `/notifications/config.ini` - Email service config - `/sentry.ini` - Shared Sentry config ## Documentation - Full implementation: `/dev/active/email-sentry-integration/` - Form service docs: `/blog-api/docs/sentry-integration.md` - Email service docs: `/notifications/docs/sentry-integration.md` ## Related Skills - Use **database-verification** before database operations - Use **workflow-builder** for workflow error context - Use **database-scripts** for database error handling ================================================ FILE: .claude/skills/frontend-dev-guidelines/SKILL.md ================================================ --- name: frontend-dev-guidelines description: Frontend development guidelines for React/TypeScript applications. Modern patterns including Suspense, lazy loading, useSuspenseQuery, file organization with features directory, MUI v7 styling, TanStack Router, performance optimization, and TypeScript best practices. Use when creating components, pages, features, fetching data, styling, routing, or working with frontend code. --- # Frontend Development Guidelines ## Purpose Comprehensive guide for modern React development, emphasizing Suspense-based data fetching, lazy loading, proper file organization, and performance optimization. ## When to Use This Skill - Creating new components or pages - Building new features - Fetching data with TanStack Query - Setting up routing with TanStack Router - Styling components with MUI v7 - Performance optimization - Organizing frontend code - TypeScript best practices --- ## Quick Start ### New Component Checklist Creating a component? Follow this checklist: - [ ] Use `React.FC` pattern with TypeScript - [ ] Lazy load if heavy component: `React.lazy(() => import())` - [ ] Wrap in `` for loading states - [ ] Use `useSuspenseQuery` for data fetching - [ ] Import aliases: `@/`, `~types`, `~components`, `~features` - [ ] Styles: Inline if <100 lines, separate file if >100 lines - [ ] Use `useCallback` for event handlers passed to children - [ ] Default export at bottom - [ ] No early returns with loading spinners - [ ] Use `useMuiSnackbar` for user notifications ### New Feature Checklist Creating a feature? Set up this structure: - [ ] Create `features/{feature-name}/` directory - [ ] Create subdirectories: `api/`, `components/`, `hooks/`, `helpers/`, `types/` - [ ] Create API service file: `api/{feature}Api.ts` - [ ] Set up TypeScript types in `types/` - [ ] Create route in `routes/{feature-name}/index.tsx` - [ ] Lazy load feature components - [ ] Use Suspense boundaries - [ ] Export public API from feature `index.ts` --- ## Import Aliases Quick Reference | Alias | Resolves To | Example | |-------|-------------|---------| | `@/` | `src/` | `import { apiClient } from '@/lib/apiClient'` | | `~types` | `src/types` | `import type { User } from '~types/user'` | | `~components` | `src/components` | `import { SuspenseLoader } from '~components/SuspenseLoader'` | | `~features` | `src/features` | `import { authApi } from '~features/auth'` | Defined in: [vite.config.ts](../../vite.config.ts) lines 180-185 --- ## Common Imports Cheatsheet ```typescript // React & Lazy Loading import React, { useState, useCallback, useMemo } from 'react'; const Heavy = React.lazy(() => import('./Heavy')); // MUI Components import { Box, Paper, Typography, Button, Grid } from '@mui/material'; import type { SxProps, Theme } from '@mui/material'; // TanStack Query (Suspense) import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query'; // TanStack Router import { createFileRoute } from '@tanstack/react-router'; // Project Components import { SuspenseLoader } from '~components/SuspenseLoader'; // Hooks import { useAuth } from '@/hooks/useAuth'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; // Types import type { Post } from '~types/post'; ``` --- ## Topic Guides ### 🎨 Component Patterns **Modern React components use:** - `React.FC` for type safety - `React.lazy()` for code splitting - `SuspenseLoader` for loading states - Named const + default export pattern **Key Concepts:** - Lazy load heavy components (DataGrid, charts, editors) - Always wrap lazy components in Suspense - Use SuspenseLoader component (with fade animation) - Component structure: Props → Hooks → Handlers → Render → Export **[📖 Complete Guide: resources/component-patterns.md](resources/component-patterns.md)** --- ### 📊 Data Fetching **PRIMARY PATTERN: useSuspenseQuery** - Use with Suspense boundaries - Cache-first strategy (check grid cache before API) - Replaces `isLoading` checks - Type-safe with generics **API Service Layer:** - Create `features/{feature}/api/{feature}Api.ts` - Use `apiClient` axios instance - Centralized methods per feature - Route format: `/form/route` (NOT `/api/form/route`) **[📖 Complete Guide: resources/data-fetching.md](resources/data-fetching.md)** --- ### 📁 File Organization **features/ vs components/:** - `features/`: Domain-specific (posts, comments, auth) - `components/`: Truly reusable (SuspenseLoader, CustomAppBar) **Feature Subdirectories:** ``` features/ my-feature/ api/ # API service layer components/ # Feature components hooks/ # Custom hooks helpers/ # Utility functions types/ # TypeScript types ``` **[📖 Complete Guide: resources/file-organization.md](resources/file-organization.md)** --- ### 🎨 Styling **Inline vs Separate:** - <100 lines: Inline `const styles: Record>` - >100 lines: Separate `.styles.ts` file **Primary Method:** - Use `sx` prop for MUI components - Type-safe with `SxProps` - Theme access: `(theme) => theme.palette.primary.main` **MUI v7 Grid:** ```typescript // ✅ v7 syntax // ❌ Old syntax ``` **[📖 Complete Guide: resources/styling-guide.md](resources/styling-guide.md)** --- ### 🛣️ Routing **TanStack Router - Folder-Based:** - Directory: `routes/my-route/index.tsx` - Lazy load components - Use `createFileRoute` - Breadcrumb data in loader **Example:** ```typescript import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; const MyPage = lazy(() => import('@/features/my-feature/components/MyPage')); export const Route = createFileRoute('/my-route/')({ component: MyPage, loader: () => ({ crumb: 'My Route' }), }); ``` **[📖 Complete Guide: resources/routing-guide.md](resources/routing-guide.md)** --- ### ⏳ Loading & Error States **CRITICAL RULE: No Early Returns** ```typescript // ❌ NEVER - Causes layout shift if (isLoading) { return ; } // ✅ ALWAYS - Consistent layout ``` **Why:** Prevents Cumulative Layout Shift (CLS), better UX **Error Handling:** - Use `useMuiSnackbar` for user feedback - NEVER `react-toastify` - TanStack Query `onError` callbacks **[📖 Complete Guide: resources/loading-and-error-states.md](resources/loading-and-error-states.md)** --- ### ⚡ Performance **Optimization Patterns:** - `useMemo`: Expensive computations (filter, sort, map) - `useCallback`: Event handlers passed to children - `React.memo`: Expensive components - Debounced search (300-500ms) - Memory leak prevention (cleanup in useEffect) **[📖 Complete Guide: resources/performance.md](resources/performance.md)** --- ### 📘 TypeScript **Standards:** - Strict mode, no `any` type - Explicit return types on functions - Type imports: `import type { User } from '~types/user'` - Component prop interfaces with JSDoc **[📖 Complete Guide: resources/typescript-standards.md](resources/typescript-standards.md)** --- ### 🔧 Common Patterns **Covered Topics:** - React Hook Form with Zod validation - DataGrid wrapper contracts - Dialog component standards - `useAuth` hook for current user - Mutation patterns with cache invalidation **[📖 Complete Guide: resources/common-patterns.md](resources/common-patterns.md)** --- ### 📚 Complete Examples **Full working examples:** - Modern component with all patterns - Complete feature structure - API service layer - Route with lazy loading - Suspense + useSuspenseQuery - Form with validation **[📖 Complete Guide: resources/complete-examples.md](resources/complete-examples.md)** --- ## Navigation Guide | Need to... | Read this resource | |------------|-------------------| | Create a component | [component-patterns.md](resources/component-patterns.md) | | Fetch data | [data-fetching.md](resources/data-fetching.md) | | Organize files/folders | [file-organization.md](resources/file-organization.md) | | Style components | [styling-guide.md](resources/styling-guide.md) | | Set up routing | [routing-guide.md](resources/routing-guide.md) | | Handle loading/errors | [loading-and-error-states.md](resources/loading-and-error-states.md) | | Optimize performance | [performance.md](resources/performance.md) | | TypeScript types | [typescript-standards.md](resources/typescript-standards.md) | | Forms/Auth/DataGrid | [common-patterns.md](resources/common-patterns.md) | | See full examples | [complete-examples.md](resources/complete-examples.md) | --- ## Core Principles 1. **Lazy Load Everything Heavy**: Routes, DataGrid, charts, editors 2. **Suspense for Loading**: Use SuspenseLoader, not early returns 3. **useSuspenseQuery**: Primary data fetching pattern for new code 4. **Features are Organized**: api/, components/, hooks/, helpers/ subdirs 5. **Styles Based on Size**: <100 inline, >100 separate 6. **Import Aliases**: Use @/, ~types, ~components, ~features 7. **No Early Returns**: Prevents layout shift 8. **useMuiSnackbar**: For all user notifications --- ## Quick Reference: File Structure ``` src/ features/ my-feature/ api/ myFeatureApi.ts # API service components/ MyFeature.tsx # Main component SubComponent.tsx # Related components hooks/ useMyFeature.ts # Custom hooks useSuspenseMyFeature.ts # Suspense hooks helpers/ myFeatureHelpers.ts # Utilities types/ index.ts # TypeScript types index.ts # Public exports components/ SuspenseLoader/ SuspenseLoader.tsx # Reusable loader CustomAppBar/ CustomAppBar.tsx # Reusable app bar routes/ my-route/ index.tsx # Route component create/ index.tsx # Nested route ``` --- ## Modern Component Template (Quick Copy) ```typescript import React, { useState, useCallback } from 'react'; import { Box, Paper } from '@mui/material'; import { useSuspenseQuery } from '@tanstack/react-query'; import { featureApi } from '../api/featureApi'; import type { FeatureData } from '~types/feature'; interface MyComponentProps { id: number; onAction?: () => void; } export const MyComponent: React.FC = ({ id, onAction }) => { const [state, setState] = useState(''); const { data } = useSuspenseQuery({ queryKey: ['feature', id], queryFn: () => featureApi.getFeature(id), }); const handleAction = useCallback(() => { setState('updated'); onAction?.(); }, [onAction]); return ( {/* Content */} ); }; export default MyComponent; ``` For complete examples, see [resources/complete-examples.md](resources/complete-examples.md) --- ## Related Skills - **error-tracking**: Error tracking with Sentry (applies to frontend too) - **backend-dev-guidelines**: Backend API patterns that frontend consumes --- **Skill Status**: Modular structure with progressive loading for optimal context management ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/common-patterns.md ================================================ # Common Patterns Frequently used patterns for forms, authentication, DataGrid, dialogs, and other common UI elements. --- ## Authentication with useAuth ### Getting Current User ```typescript import { useAuth } from '@/hooks/useAuth'; export const MyComponent: React.FC = () => { const { user } = useAuth(); // Available properties: // - user.id: string // - user.email: string // - user.username: string // - user.roles: string[] return (

Logged in as: {user.email}

Username: {user.username}

Roles: {user.roles.join(', ')}

); }; ``` **NEVER make direct API calls for auth** - always use `useAuth` hook. --- ## Forms with React Hook Form ### Basic Form ```typescript import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { TextField, Button } from '@mui/material'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; // Zod schema for validation const formSchema = z.object({ username: z.string().min(3, 'Username must be at least 3 characters'), email: z.string().email('Invalid email address'), age: z.number().min(18, 'Must be 18 or older'), }); type FormData = z.infer; export const MyForm: React.FC = () => { const { showSuccess, showError } = useMuiSnackbar(); const { register, handleSubmit, formState: { errors } } = useForm({ resolver: zodResolver(formSchema), defaultValues: { username: '', email: '', age: 18, }, }); const onSubmit = async (data: FormData) => { try { await api.submitForm(data); showSuccess('Form submitted successfully'); } catch (error) { showError('Failed to submit form'); } }; return (
); }; ``` --- ## Dialog Component Pattern ### Standard Dialog Structure From BEST_PRACTICES.md - All dialogs should have: - Icon in title - Close button (X) - Action buttons at bottom ```typescript import { Dialog, DialogTitle, DialogContent, DialogActions, Button, IconButton } from '@mui/material'; import { Close, Info } from '@mui/icons-material'; interface MyDialogProps { open: boolean; onClose: () => void; onConfirm: () => void; } export const MyDialog: React.FC = ({ open, onClose, onConfirm }) => { return ( Dialog Title {/* Content here */} ); }; ``` --- ## DataGrid Wrapper Pattern ### Wrapper Component Contract From BEST_PRACTICES.md - DataGrid wrappers should accept: **Required Props:** - `rows`: Data array - `columns`: Column definitions - Loading/error states **Optional Props:** - Toolbar components - Custom actions - Initial state ```typescript import { DataGridPro } from '@mui/x-data-grid-pro'; import type { GridColDef } from '@mui/x-data-grid-pro'; interface DataGridWrapperProps { rows: any[]; columns: GridColDef[]; loading?: boolean; toolbar?: React.ReactNode; onRowClick?: (row: any) => void; } export const DataGridWrapper: React.FC = ({ rows, columns, loading = false, toolbar, onRowClick, }) => { return ( toolbar : undefined }} onRowClick={(params) => onRowClick?.(params.row)} // Standard configuration pagination pageSizeOptions={[25, 50, 100]} initialState={{ pagination: { paginationModel: { pageSize: 25 } }, }} /> ); }; ``` --- ## Mutation Patterns ### Update with Cache Invalidation ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; export const useUpdateEntity = () => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); return useMutation({ mutationFn: ({ id, data }: { id: number; data: any }) => api.updateEntity(id, data), onSuccess: (result, variables) => { // Invalidate affected queries queryClient.invalidateQueries({ queryKey: ['entity', variables.id] }); queryClient.invalidateQueries({ queryKey: ['entities'] }); showSuccess('Entity updated'); }, onError: () => { showError('Failed to update entity'); }, }); }; // Usage const updateEntity = useUpdateEntity(); const handleSave = () => { updateEntity.mutate({ id: 123, data: { name: 'New Name' } }); }; ``` --- ## State Management Patterns ### TanStack Query for Server State (PRIMARY) Use TanStack Query for **all server data**: - Fetching: useSuspenseQuery - Mutations: useMutation - Caching: Automatic - Synchronization: Built-in ```typescript // ✅ CORRECT - TanStack Query for server data const { data: users } = useSuspenseQuery({ queryKey: ['users'], queryFn: () => userApi.getUsers(), }); ``` ### useState for UI State Use `useState` for **local UI state only**: - Form inputs (uncontrolled) - Modal open/closed - Selected tab - Temporary UI flags ```typescript // ✅ CORRECT - useState for UI state const [modalOpen, setModalOpen] = useState(false); const [selectedTab, setSelectedTab] = useState(0); ``` ### Zustand for Global Client State (Minimal) Use Zustand only for **global client state**: - Theme preference - Sidebar collapsed state - User preferences (not from server) ```typescript import { create } from 'zustand'; interface AppState { sidebarOpen: boolean; toggleSidebar: () => void; } export const useAppState = create((set) => ({ sidebarOpen: true, toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })), })); ``` **Avoid prop drilling** - use context or Zustand instead. --- ## Summary **Common Patterns:** - ✅ useAuth hook for current user (id, email, roles, username) - ✅ React Hook Form + Zod for forms - ✅ Dialog with icon + close button - ✅ DataGrid wrapper contracts - ✅ Mutations with cache invalidation - ✅ TanStack Query for server state - ✅ useState for UI state - ✅ Zustand for global client state (minimal) **See Also:** - [data-fetching.md](data-fetching.md) - TanStack Query patterns - [component-patterns.md](component-patterns.md) - Component structure - [loading-and-error-states.md](loading-and-error-states.md) - Error handling ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/complete-examples.md ================================================ # Complete Examples Full working examples combining all modern patterns: React.FC, lazy loading, Suspense, useSuspenseQuery, styling, routing, and error handling. --- ## Example 1: Complete Modern Component Combines: React.FC, useSuspenseQuery, cache-first, useCallback, styling, error handling ```typescript /** * User profile display component * Demonstrates modern patterns with Suspense and TanStack Query */ import React, { useState, useCallback, useMemo } from 'react'; import { Box, Paper, Typography, Button, Avatar } from '@mui/material'; import type { SxProps, Theme } from '@mui/material'; import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { userApi } from '../api/userApi'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; import type { User } from '~types/user'; // Styles object const componentStyles: Record> = { container: { p: 3, maxWidth: 600, margin: '0 auto', }, header: { display: 'flex', alignItems: 'center', gap: 2, mb: 3, }, content: { display: 'flex', flexDirection: 'column', gap: 2, }, actions: { display: 'flex', gap: 1, mt: 2, }, }; interface UserProfileProps { userId: string; onUpdate?: () => void; } export const UserProfile: React.FC = ({ userId, onUpdate }) => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); const [isEditing, setIsEditing] = useState(false); // Suspense query - no isLoading needed! const { data: user } = useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => userApi.getUser(userId), staleTime: 5 * 60 * 1000, }); // Update mutation const updateMutation = useMutation({ mutationFn: (updates: Partial) => userApi.updateUser(userId, updates), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['user', userId] }); showSuccess('Profile updated'); setIsEditing(false); onUpdate?.(); }, onError: () => { showError('Failed to update profile'); }, }); // Memoized computed value const fullName = useMemo(() => { return `${user.firstName} ${user.lastName}`; }, [user.firstName, user.lastName]); // Event handlers with useCallback const handleEdit = useCallback(() => { setIsEditing(true); }, []); const handleSave = useCallback(() => { updateMutation.mutate({ firstName: user.firstName, lastName: user.lastName, }); }, [user, updateMutation]); const handleCancel = useCallback(() => { setIsEditing(false); }, []); return ( {user.firstName[0]}{user.lastName[0]} {fullName} {user.email} Username: {user.username} Roles: {user.roles.join(', ')} {!isEditing ? ( ) : ( <> )} ); }; export default UserProfile; ``` **Usage:** ```typescript console.log('Updated')} /> ``` --- ## Example 2: Complete Feature Structure Real example based on `features/posts/`: ``` features/ users/ api/ userApi.ts # API service layer components/ UserProfile.tsx # Main component (from Example 1) UserList.tsx # List component UserBlog.tsx # Blog component modals/ DeleteUserModal.tsx # Modal component hooks/ useSuspenseUser.ts # Suspense query hook useUserMutations.ts # Mutation hooks useUserPermissions.ts # Feature-specific hook helpers/ userHelpers.ts # Utility functions validation.ts # Validation logic types/ index.ts # TypeScript interfaces index.ts # Public API exports ``` ### API Service (userApi.ts) ```typescript import apiClient from '@/lib/apiClient'; import type { User, CreateUserPayload, UpdateUserPayload } from '../types'; export const userApi = { getUser: async (userId: string): Promise => { const { data } = await apiClient.get(`/users/${userId}`); return data; }, getUsers: async (): Promise => { const { data } = await apiClient.get('/users'); return data; }, createUser: async (payload: CreateUserPayload): Promise => { const { data } = await apiClient.post('/users', payload); return data; }, updateUser: async (userId: string, payload: UpdateUserPayload): Promise => { const { data } = await apiClient.put(`/users/${userId}`, payload); return data; }, deleteUser: async (userId: string): Promise => { await apiClient.delete(`/users/${userId}`); }, }; ``` ### Suspense Hook (useSuspenseUser.ts) ```typescript import { useSuspenseQuery } from '@tanstack/react-query'; import { userApi } from '../api/userApi'; import type { User } from '../types'; export function useSuspenseUser(userId: string) { return useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => userApi.getUser(userId), staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, }); } export function useSuspenseUsers() { return useSuspenseQuery({ queryKey: ['users'], queryFn: () => userApi.getUsers(), staleTime: 1 * 60 * 1000, // Shorter for list }); } ``` ### Types (types/index.ts) ```typescript export interface User { id: string; username: string; email: string; firstName: string; lastName: string; roles: string[]; createdAt: string; updatedAt: string; } export interface CreateUserPayload { username: string; email: string; firstName: string; lastName: string; password: string; } export type UpdateUserPayload = Partial>; ``` ### Public Exports (index.ts) ```typescript // Export components export { UserProfile } from './components/UserProfile'; export { UserList } from './components/UserList'; // Export hooks export { useSuspenseUser, useSuspenseUsers } from './hooks/useSuspenseUser'; export { useUserMutations } from './hooks/useUserMutations'; // Export API export { userApi } from './api/userApi'; // Export types export type { User, CreateUserPayload, UpdateUserPayload } from './types'; ``` --- ## Example 3: Complete Route with Lazy Loading ```typescript /** * User profile route * Path: /users/:userId */ import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; import { SuspenseLoader } from '~components/SuspenseLoader'; // Lazy load the UserProfile component const UserProfile = lazy(() => import('@/features/users/components/UserProfile').then( (module) => ({ default: module.UserProfile }) ) ); export const Route = createFileRoute('/users/$userId')({ component: UserProfilePage, loader: ({ params }) => ({ crumb: `User ${params.userId}`, }), }); function UserProfilePage() { const { userId } = Route.useParams(); return ( console.log('Profile updated')} /> ); } export default UserProfilePage; ``` --- ## Example 4: List with Search and Filtering ```typescript import React, { useState, useMemo } from 'react'; import { Box, TextField, List, ListItem } from '@mui/material'; import { useDebounce } from 'use-debounce'; import { useSuspenseQuery } from '@tanstack/react-query'; import { userApi } from '../api/userApi'; export const UserList: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearch] = useDebounce(searchTerm, 300); const { data: users } = useSuspenseQuery({ queryKey: ['users'], queryFn: () => userApi.getUsers(), }); // Memoized filtering const filteredUsers = useMemo(() => { if (!debouncedSearch) return users; return users.filter(user => user.name.toLowerCase().includes(debouncedSearch.toLowerCase()) || user.email.toLowerCase().includes(debouncedSearch.toLowerCase()) ); }, [users, debouncedSearch]); return ( setSearchTerm(e.target.value)} placeholder='Search users...' fullWidth sx={{ mb: 2 }} /> {filteredUsers.map(user => ( {user.name} - {user.email} ))} ); }; ``` --- ## Example 5: Blog with Validation ```typescript import React from 'react'; import { Box, TextField, Button, Paper } from '@mui/material'; import { useBlog } from 'react-hook-blog'; import { zodResolver } from '@hookblog/resolvers/zod'; import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { userApi } from '../api/userApi'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; const userSchema = z.object({ username: z.string().min(3).max(50), email: z.string().email(), firstName: z.string().min(1), lastName: z.string().min(1), }); type UserBlogData = z.infer; interface CreateUserBlogProps { onSuccess?: () => void; } export const CreateUserBlog: React.FC = ({ onSuccess }) => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); const { register, handleSubmit, blogState: { errors }, reset } = useBlog({ resolver: zodResolver(userSchema), defaultValues: { username: '', email: '', firstName: '', lastName: '', }, }); const createMutation = useMutation({ mutationFn: (data: UserBlogData) => userApi.createUser(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); showSuccess('User created successfully'); reset(); onSuccess?.(); }, onError: () => { showError('Failed to create user'); }, }); const onSubmit = (data: UserBlogData) => { createMutation.mutate(data); }; return ( ); }; export default CreateUserBlog; ``` --- ## Example 2: Parent Container with Lazy Loading ```typescript import React from 'react'; import { Box } from '@mui/material'; import { SuspenseLoader } from '~components/SuspenseLoader'; // Lazy load heavy components const UserList = React.lazy(() => import('./UserList')); const UserStats = React.lazy(() => import('./UserStats')); const ActivityFeed = React.lazy(() => import('./ActivityFeed')); export const UserDashboard: React.FC = () => { return ( ); }; export default UserDashboard; ``` **Benefits:** - Each section loads independently - User sees partial content sooner - Better perceived perblogance --- ## Example 3: Cache-First Strategy Implementation Complete example based on useSuspensePost.ts: ```typescript import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query'; import { postApi } from '../api/postApi'; import type { Post } from '../types'; /** * Smart post hook with cache-first strategy * Reuses data from grid cache when available */ export function useSuspensePost(blogId: number, postId: number) { const queryClient = useQueryClient(); return useSuspenseQuery({ queryKey: ['post', blogId, postId], queryFn: async () => { // Strategy 1: Check grid cache first (avoids API call) const gridCache = queryClient.getQueryData<{ rows: Post[] }>([ 'posts-v2', blogId, 'summary' ]) || queryClient.getQueryData<{ rows: Post[] }>([ 'posts-v2', blogId, 'flat' ]); if (gridCache?.rows) { const cached = gridCache.rows.find( (row) => row.S_ID === postId ); if (cached) { return cached; // Return from cache - no API call! } } // Strategy 2: Not in cache, fetch from API return postApi.getPost(blogId, postId); }, staleTime: 5 * 60 * 1000, // Fresh for 5 minutes gcTime: 10 * 60 * 1000, // Cache for 10 minutes refetchOnWindowFocus: false, // Don't refetch on focus }); } ``` **Why this pattern:** - Checks grid cache before API - Instant data if user came from grid - Falls back to API if not cached - Configurable cache times --- ## Example 4: Complete Route File ```typescript /** * Project catalog route * Path: /project-catalog */ import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; // Lazy load the PostTable component const PostTable = lazy(() => import('@/features/posts/components/PostTable').then( (module) => ({ default: module.PostTable }) ) ); // Route constants const PROJECT_CATALOG_FORM_ID = 744; const PROJECT_CATALOG_PROJECT_ID = 225; export const Route = createFileRoute('/project-catalog/')({ component: ProjectCatalogPage, loader: () => ({ crumb: 'Projects', // Breadcrumb title }), }); function ProjectCatalogPage() { return ( ); } export default ProjectCatalogPage; ``` --- ## Example 5: Dialog with Blog ```typescript import React from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField, Box, IconButton, } from '@mui/material'; import { Close, PersonAdd } from '@mui/icons-material'; import { useBlog } from 'react-hook-blog'; import { zodResolver } from '@hookblog/resolvers/zod'; import { z } from 'zod'; const blogSchema = z.object({ name: z.string().min(1), email: z.string().email(), }); type BlogData = z.infer; interface AddUserDialogProps { open: boolean; onClose: () => void; onSubmit: (data: BlogData) => Promise; } export const AddUserDialog: React.FC = ({ open, onClose, onSubmit, }) => { const { register, handleSubmit, blogState: { errors }, reset } = useBlog({ resolver: zodResolver(blogSchema), }); const handleClose = () => { reset(); onClose(); }; const handleBlogSubmit = async (data: BlogData) => { await onSubmit(data); handleClose(); }; return ( Add User ); }; ``` --- ## Example 6: Parallel Data Fetching ```typescript import React from 'react'; import { Box, Grid, Paper } from '@mui/material'; import { useSuspenseQueries } from '@tanstack/react-query'; import { userApi } from '../api/userApi'; import { statsApi } from '../api/statsApi'; import { activityApi } from '../api/activityApi'; export const Dashboard: React.FC = () => { // Fetch all data in parallel with Suspense const [statsQuery, usersQuery, activityQuery] = useSuspenseQueries({ queries: [ { queryKey: ['stats'], queryFn: () => statsApi.getStats(), }, { queryKey: ['users', 'active'], queryFn: () => userApi.getActiveUsers(), }, { queryKey: ['activity', 'recent'], queryFn: () => activityApi.getRecent(), }, ], }); return (

Stats

Total: {statsQuery.data.total}

Active Users

Count: {usersQuery.data.length}

Recent Activity

Events: {activityQuery.data.length}

); }; // Usage with Suspense ``` --- ## Example 7: Optimistic Update ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query'; import type { User } from '../types'; export const useToggleUserStatus = () => { const queryClient = useQueryClient(); return useMutation({ mutationFn: (userId: string) => userApi.toggleStatus(userId), // Optimistic update onMutate: async (userId) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['users'] }); // Snapshot previous value const previousUsers = queryClient.getQueryData(['users']); // Optimistically update UI queryClient.setQueryData(['users'], (old) => { return old?.map(user => user.id === userId ? { ...user, active: !user.active } : user ) || []; }); return { previousUsers }; }, // Rollback on error onError: (err, userId, context) => { queryClient.setQueryData(['users'], context?.previousUsers); }, // Refetch after mutation onSettled: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); }; ``` --- ## Summary **Key Takeaways:** 1. **Component Pattern**: React.FC + lazy + Suspense + useSuspenseQuery 2. **Feature Structure**: Organized subdirectories (api/, components/, hooks/, etc.) 3. **Routing**: Folder-based with lazy loading 4. **Data Fetching**: useSuspenseQuery with cache-first strategy 5. **Blogs**: React Hook Blog + Zod validation 6. **Error Handling**: useMuiSnackbar + onError callbacks 7. **Perblogance**: useMemo, useCallback, React.memo, debouncing 8. **Styling**: Inline <100 lines, sx prop, MUI v7 syntax **See other resources for detailed explanations of each pattern.** ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/component-patterns.md ================================================ # Component Patterns Modern React component architecture for the application emphasizing type safety, lazy loading, and Suspense boundaries. --- ## React.FC Pattern (PREFERRED) ### Why React.FC All components use the `React.FC` pattern for: - Explicit type safety for props - Consistent component signatures - Clear prop interface documentation - Better IDE autocomplete ### Basic Pattern ```typescript import React from 'react'; interface MyComponentProps { /** User ID to display */ userId: number; /** Optional callback when action occurs */ onAction?: () => void; } export const MyComponent: React.FC = ({ userId, onAction }) => { return (
User: {userId}
); }; export default MyComponent; ``` **Key Points:** - Props interface defined separately with JSDoc comments - `React.FC` provides type safety - Destructure props in parameters - Default export at bottom --- ## Lazy Loading Pattern ### When to Lazy Load Lazy load components that are: - Heavy (DataGrid, charts, rich text editors) - Route-level components - Modal/dialog content (not shown initially) - Below-the-fold content ### How to Lazy Load ```typescript import React from 'react'; // Lazy load heavy component const PostDataGrid = React.lazy(() => import('./grids/PostDataGrid') ); // For named exports const MyComponent = React.lazy(() => import('./MyComponent').then(module => ({ default: module.MyComponent })) ); ``` **Example from PostTable.tsx:** ```typescript /** * Main post table container component */ import React, { useState, useCallback } from 'react'; import { Box, Paper } from '@mui/material'; // Lazy load PostDataGrid to optimize bundle size const PostDataGrid = React.lazy(() => import('./grids/PostDataGrid')); import { SuspenseLoader } from '~components/SuspenseLoader'; export const PostTable: React.FC = ({ formId }) => { return ( ); }; export default PostTable; ``` --- ## Suspense Boundaries ### SuspenseLoader Component **Import:** ```typescript import { SuspenseLoader } from '~components/SuspenseLoader'; // Or import { SuspenseLoader } from '@/components/SuspenseLoader'; ``` **Usage:** ```typescript ``` **What it does:** - Shows loading indicator while lazy component loads - Smooth fade-in animation - Consistent loading experience - Prevents layout shift ### Where to Place Suspense Boundaries **Route Level:** ```typescript // routes/my-route/index.tsx const MyPage = lazy(() => import('@/features/my-feature/components/MyPage')); function Route() { return ( ); } ``` **Component Level:** ```typescript function ParentComponent() { return (
); } ``` **Multiple Boundaries:** ```typescript function Page() { return ( ); } ``` Each section loads independently, better UX. --- ## Component Structure Template ### Recommended Order ```typescript /** * Component description * What it does, when to use it */ import React, { useState, useCallback, useMemo, useEffect } from 'react'; import { Box, Paper, Button } from '@mui/material'; import type { SxProps, Theme } from '@mui/material'; import { useSuspenseQuery } from '@tanstack/react-query'; // Feature imports import { myFeatureApi } from '../api/myFeatureApi'; import type { MyData } from '~types/myData'; // Component imports import { SuspenseLoader } from '~components/SuspenseLoader'; // Hooks import { useAuth } from '@/hooks/useAuth'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; // 1. PROPS INTERFACE (with JSDoc) interface MyComponentProps { /** The ID of the entity to display */ entityId: number; /** Optional callback when action completes */ onComplete?: () => void; /** Display mode */ mode?: 'view' | 'edit'; } // 2. STYLES (if inline and <100 lines) const componentStyles: Record> = { container: { p: 2, display: 'flex', flexDirection: 'column', }, header: { mb: 2, display: 'flex', justifyContent: 'space-between', }, }; // 3. COMPONENT DEFINITION export const MyComponent: React.FC = ({ entityId, onComplete, mode = 'view', }) => { // 4. HOOKS (in this order) // - Context hooks first const { user } = useAuth(); const { showSuccess, showError } = useMuiSnackbar(); // - Data fetching const { data } = useSuspenseQuery({ queryKey: ['myEntity', entityId], queryFn: () => myFeatureApi.getEntity(entityId), }); // - Local state const [selectedItem, setSelectedItem] = useState(null); const [isEditing, setIsEditing] = useState(mode === 'edit'); // - Memoized values const filteredData = useMemo(() => { return data.filter(item => item.active); }, [data]); // - Effects useEffect(() => { // Setup return () => { // Cleanup }; }, []); // 5. EVENT HANDLERS (with useCallback) const handleItemSelect = useCallback((itemId: string) => { setSelectedItem(itemId); }, []); const handleSave = useCallback(async () => { try { await myFeatureApi.updateEntity(entityId, { /* data */ }); showSuccess('Entity updated successfully'); onComplete?.(); } catch (error) { showError('Failed to update entity'); } }, [entityId, onComplete, showSuccess, showError]); // 6. RENDER return (

My Component

{filteredData.map(item => (
{item.name}
))}
); }; // 7. EXPORT (default export at bottom) export default MyComponent; ``` --- ## Component Separation ### When to Split Components **Split into multiple components when:** - Component exceeds 300 lines - Multiple distinct responsibilities - Reusable sections - Complex nested JSX **Example:** ```typescript // ❌ AVOID - Monolithic function MassiveComponent() { // 500+ lines // Search logic // Filter logic // Grid logic // Action panel logic } // ✅ PREFERRED - Modular function ParentContainer() { return ( ); } ``` ### When to Keep Together **Keep in same file when:** - Component < 200 lines - Tightly coupled logic - Not reusable elsewhere - Simple presentation component --- ## Export Patterns ### Named Const + Default Export (PREFERRED) ```typescript export const MyComponent: React.FC = ({ ... }) => { // Component logic }; export default MyComponent; ``` **Why:** - Named export for testing/refactoring - Default export for lazy loading convenience - Both options available to consumers ### Lazy Loading Named Exports ```typescript const MyComponent = React.lazy(() => import('./MyComponent').then(module => ({ default: module.MyComponent })) ); ``` --- ## Component Communication ### Props Down, Events Up ```typescript // Parent function Parent() { const [selectedId, setSelectedId] = useState(null); return ( ); } // Child interface ChildProps { data: Data[]; onSelect: (id: string) => void; } export const Child: React.FC = ({ data, onSelect }) => { return (
onSelect(data[0].id)}> {/* Content */}
); }; ``` ### Avoid Prop Drilling **Use context for deep nesting:** ```typescript // ❌ AVOID - Prop drilling 5+ levels // Finally uses it here // ✅ PREFERRED - Context or TanStack Query const MyContext = createContext(null); function Provider({ children }) { const { data } = useSuspenseQuery({ ... }); return {children}; } function DeepChild() { const data = useContext(MyContext); // Use data directly } ``` --- ## Advanced Patterns ### Compound Components ```typescript // Card.tsx export const Card: React.FC & { Header: typeof CardHeader; Body: typeof CardBody; Footer: typeof CardFooter; } = ({ children }) => { return {children}; }; Card.Header = CardHeader; Card.Body = CardBody; Card.Footer = CardFooter; // Usage Title Content Actions ``` ### Render Props (Rare, but useful) ```typescript interface DataProviderProps { children: (data: Data) => React.ReactNode; } export const DataProvider: React.FC = ({ children }) => { const { data } = useSuspenseQuery({ ... }); return <>{children(data)}; }; // Usage {(data) => } ``` --- ## Summary **Modern Component Recipe:** 1. `React.FC` with TypeScript 2. Lazy load if heavy: `React.lazy(() => import())` 3. Wrap in `` for loading 4. Use `useSuspenseQuery` for data 5. Import aliases (@/, ~types, ~components) 6. Event handlers with `useCallback` 7. Default export at bottom 8. No early returns for loading states **See Also:** - [data-fetching.md](data-fetching.md) - useSuspenseQuery details - [loading-and-error-states.md](loading-and-error-states.md) - Suspense best practices - [complete-examples.md](complete-examples.md) - Full working examples ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/data-fetching.md ================================================ # Data Fetching Patterns Modern data fetching using TanStack Query with Suspense boundaries, cache-first strategies, and centralized API services. --- ## PRIMARY PATTERN: useSuspenseQuery ### Why useSuspenseQuery? For **all new components**, use `useSuspenseQuery` instead of regular `useQuery`: **Benefits:** - No `isLoading` checks needed - Integrates with Suspense boundaries - Cleaner component code - Consistent loading UX - Better error handling with error boundaries ### Basic Pattern ```typescript import { useSuspenseQuery } from '@tanstack/react-query'; import { myFeatureApi } from '../api/myFeatureApi'; export const MyComponent: React.FC = ({ id }) => { // No isLoading - Suspense handles it! const { data } = useSuspenseQuery({ queryKey: ['myEntity', id], queryFn: () => myFeatureApi.getEntity(id), }); // data is ALWAYS defined here (not undefined | Data) return
{data.name}
; }; // Wrap in Suspense boundary ``` ### useSuspenseQuery vs useQuery | Feature | useSuspenseQuery | useQuery | |---------|------------------|----------| | Loading state | Handled by Suspense | Manual `isLoading` check | | Data type | Always defined | `Data \| undefined` | | Use with | Suspense boundaries | Traditional components | | Recommended for | **NEW components** | Legacy code only | | Error handling | Error boundaries | Manual error state | **When to use regular useQuery:** - Maintaining legacy code - Very simple cases without Suspense - Polling with background updates **For new components: Always prefer useSuspenseQuery** --- ## Cache-First Strategy ### Cache-First Pattern Example **Smart caching** reduces API calls by checking React Query cache first: ```typescript import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query'; import { postApi } from '../api/postApi'; export function useSuspensePost(postId: number) { const queryClient = useQueryClient(); return useSuspenseQuery({ queryKey: ['post', postId], queryFn: async () => { // Strategy 1: Try to get from list cache first const cachedListData = queryClient.getQueryData<{ posts: Post[] }>([ 'posts', 'list' ]); if (cachedListData?.posts) { const cachedPost = cachedListData.posts.find( (post) => post.id === postId ); if (cachedPost) { return cachedPost; // Return from cache! } } // Strategy 2: Not in cache, fetch from API return postApi.getPost(postId); }, staleTime: 5 * 60 * 1000, // Consider fresh for 5 minutes gcTime: 10 * 60 * 1000, // Keep in cache for 10 minutes refetchOnWindowFocus: false, // Don't refetch on focus }); } ``` **Key Points:** - Check grid/list cache before API call - Avoids redundant requests - `staleTime`: How long data is considered fresh - `gcTime`: How long unused data stays in cache - `refetchOnWindowFocus: false`: User preference --- ## Parallel Data Fetching ### useSuspenseQueries When fetching multiple independent resources: ```typescript import { useSuspenseQueries } from '@tanstack/react-query'; export const MyComponent: React.FC = () => { const [userQuery, settingsQuery, preferencesQuery] = useSuspenseQueries({ queries: [ { queryKey: ['user'], queryFn: () => userApi.getCurrentUser(), }, { queryKey: ['settings'], queryFn: () => settingsApi.getSettings(), }, { queryKey: ['preferences'], queryFn: () => preferencesApi.getPreferences(), }, ], }); // All data available, Suspense handles loading const user = userQuery.data; const settings = settingsQuery.data; const preferences = preferencesQuery.data; return ; }; ``` **Benefits:** - All queries in parallel - Single Suspense boundary - Type-safe results --- ## Query Keys Organization ### Naming Convention ```typescript // Entity list ['entities', blogId] ['entities', blogId, 'summary'] // With view mode ['entities', blogId, 'flat'] // Single entity ['entity', blogId, entityId] // Related data ['entity', entityId, 'history'] ['entity', entityId, 'comments'] // User-specific ['user', userId, 'profile'] ['user', userId, 'permissions'] ``` **Rules:** - Start with entity name (plural for lists, singular for one) - Include IDs for specificity - Add view mode / relationship at end - Consistent across app ### Query Key Examples ```typescript // From useSuspensePost.ts queryKey: ['post', blogId, postId] queryKey: ['posts-v2', blogId, 'summary'] // Invalidation patterns queryClient.invalidateQueries({ queryKey: ['post', blogId] }); // All posts for form queryClient.invalidateQueries({ queryKey: ['post'] }); // All posts ``` --- ## API Service Layer Pattern ### File Structure Create centralized API service per feature: ``` features/ my-feature/ api/ myFeatureApi.ts # Service layer ``` ### Service Pattern (from postApi.ts) ```typescript /** * Centralized API service for my-feature operations * Uses apiClient for consistent error handling */ import apiClient from '@/lib/apiClient'; import type { MyEntity, UpdatePayload } from '../types'; export const myFeatureApi = { /** * Fetch a single entity */ getEntity: async (blogId: number, entityId: number): Promise => { const { data } = await apiClient.get( `/blog/entities/${blogId}/${entityId}` ); return data; }, /** * Fetch all entities for a form */ getEntities: async (blogId: number, view: 'summary' | 'flat'): Promise => { const { data } = await apiClient.get( `/blog/entities/${blogId}`, { params: { view } } ); return data.rows; }, /** * Update entity */ updateEntity: async ( blogId: number, entityId: number, payload: UpdatePayload ): Promise => { const { data } = await apiClient.put( `/blog/entities/${blogId}/${entityId}`, payload ); return data; }, /** * Delete entity */ deleteEntity: async (blogId: number, entityId: number): Promise => { await apiClient.delete(`/blog/entities/${blogId}/${entityId}`); }, }; ``` **Key Points:** - Export single object with methods - Use `apiClient` (axios instance from `@/lib/apiClient`) - Type-safe parameters and returns - JSDoc comments for each method - Centralized error handling (apiClient handles it) --- ## Route Format Rules (IMPORTANT) ### Correct Format ```typescript // ✅ CORRECT - Direct service path await apiClient.get('/blog/posts/123'); await apiClient.post('/projects/create', data); await apiClient.put('/users/update/456', updates); await apiClient.get('/email/templates'); // ❌ WRONG - Do NOT add /api/ prefix await apiClient.get('/api/blog/posts/123'); // WRONG! await apiClient.post('/api/projects/create', data); // WRONG! ``` **Microservice Routing:** - Form service: `/blog/*` - Projects service: `/projects/*` - Email service: `/email/*` - Users service: `/users/*` **Why:** API routing is handled by proxy configuration, no `/api/` prefix needed. --- ## Mutations ### Basic Mutation Pattern ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query'; import { myFeatureApi } from '../api/myFeatureApi'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; export const MyComponent: React.FC = () => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); const updateMutation = useMutation({ mutationFn: (payload: UpdatePayload) => myFeatureApi.updateEntity(blogId, entityId, payload), onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ['entity', blogId, entityId] }); showSuccess('Entity updated successfully'); }, onError: (error) => { showError('Failed to update entity'); console.error('Update error:', error); }, }); const handleUpdate = () => { updateMutation.mutate({ name: 'New Name' }); }; return ( ); }; ``` ### Optimistic Updates ```typescript const updateMutation = useMutation({ mutationFn: (payload) => myFeatureApi.update(id, payload), // Optimistic update onMutate: async (newData) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['entity', id] }); // Snapshot current value const previousData = queryClient.getQueryData(['entity', id]); // Optimistically update queryClient.setQueryData(['entity', id], (old) => ({ ...old, ...newData, })); // Return rollback function return { previousData }; }, // Rollback on error onError: (err, newData, context) => { queryClient.setQueryData(['entity', id], context.previousData); showError('Update failed'); }, // Refetch after success or error onSettled: () => { queryClient.invalidateQueries({ queryKey: ['entity', id] }); }, }); ``` --- ## Advanced Query Patterns ### Prefetching ```typescript export function usePrefetchEntity() { const queryClient = useQueryClient(); return (blogId: number, entityId: number) => { return queryClient.prefetchQuery({ queryKey: ['entity', blogId, entityId], queryFn: () => myFeatureApi.getEntity(blogId, entityId), staleTime: 5 * 60 * 1000, }); }; } // Usage: Prefetch on hover
prefetch(blogId, id)}> View
``` ### Cache Access Without Fetching ```typescript export function useEntityFromCache(blogId: number, entityId: number) { const queryClient = useQueryClient(); // Get from cache, don't fetch if missing const directCache = queryClient.getQueryData(['entity', blogId, entityId]); if (directCache) return directCache; // Try grid cache const gridCache = queryClient.getQueryData<{ rows: MyEntity[] }>(['entities-v2', blogId]); return gridCache?.rows.find(row => row.id === entityId); } ``` ### Dependent Queries ```typescript // Fetch user first, then user's settings const { data: user } = useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => userApi.getUser(userId), }); const { data: settings } = useSuspenseQuery({ queryKey: ['user', userId, 'settings'], queryFn: () => settingsApi.getUserSettings(user.id), // Automatically waits for user to load due to Suspense }); ``` --- ## API Client Configuration ### Using apiClient ```typescript import apiClient from '@/lib/apiClient'; // apiClient is a configured axios instance // Automatically includes: // - Base URL configuration // - Cookie-based authentication // - Error interceptors // - Response transformers ``` **Do NOT create new axios instances** - use apiClient for consistency. --- ## Error Handling in Queries ### onError Callback ```typescript import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; const { showError } = useMuiSnackbar(); const { data } = useSuspenseQuery({ queryKey: ['entity', id], queryFn: () => myFeatureApi.getEntity(id), // Handle errors onError: (error) => { showError('Failed to load entity'); console.error('Load error:', error); }, }); ``` ### Error Boundaries Combine with Error Boundaries for comprehensive error handling: ```typescript import { ErrorBoundary } from 'react-error-boundary'; } onError={(error) => console.error(error)} > ``` --- ## Complete Examples ### Example 1: Simple Entity Fetch ```typescript import React from 'react'; import { useSuspenseQuery } from '@tanstack/react-query'; import { Box, Typography } from '@mui/material'; import { userApi } from '../api/userApi'; interface UserProfileProps { userId: string; } export const UserProfile: React.FC = ({ userId }) => { const { data: user } = useSuspenseQuery({ queryKey: ['user', userId], queryFn: () => userApi.getUser(userId), staleTime: 5 * 60 * 1000, }); return ( {user.name} {user.email} ); }; // Usage with Suspense ``` ### Example 2: Cache-First Strategy ```typescript import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query'; import { postApi } from '../api/postApi'; import type { Post } from '../types'; /** * Hook with cache-first strategy * Checks grid cache before API call */ export function useSuspensePost(blogId: number, postId: number) { const queryClient = useQueryClient(); return useSuspenseQuery({ queryKey: ['post', blogId, postId], queryFn: async () => { // 1. Check grid cache first const gridCache = queryClient.getQueryData<{ rows: Post[] }>([ 'posts-v2', blogId, 'summary' ]) || queryClient.getQueryData<{ rows: Post[] }>([ 'posts-v2', blogId, 'flat' ]); if (gridCache?.rows) { const cached = gridCache.rows.find(row => row.S_ID === postId); if (cached) { return cached; // Reuse grid data } } // 2. Not in cache, fetch directly return postApi.getPost(blogId, postId); }, staleTime: 5 * 60 * 1000, gcTime: 10 * 60 * 1000, refetchOnWindowFocus: false, }); } ``` **Benefits:** - Avoids duplicate API calls - Instant data if already loaded - Falls back to API if not cached ### Example 3: Parallel Fetching ```typescript import { useSuspenseQueries } from '@tanstack/react-query'; export const Dashboard: React.FC = () => { const [statsQuery, projectsQuery, notificationsQuery] = useSuspenseQueries({ queries: [ { queryKey: ['stats'], queryFn: () => statsApi.getStats(), }, { queryKey: ['projects', 'active'], queryFn: () => projectsApi.getActiveProjects(), }, { queryKey: ['notifications', 'unread'], queryFn: () => notificationsApi.getUnread(), }, ], }); return ( ); }; ``` --- ## Mutations with Cache Invalidation ### Update Mutation ```typescript import { useMutation, useQueryClient } from '@tanstack/react-query'; import { postApi } from '../api/postApi'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; export const useUpdatePost = () => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); return useMutation({ mutationFn: ({ blogId, postId, data }: UpdateParams) => postApi.updatePost(blogId, postId, data), onSuccess: (data, variables) => { // Invalidate specific post queryClient.invalidateQueries({ queryKey: ['post', variables.blogId, variables.postId] }); // Invalidate list to refresh grid queryClient.invalidateQueries({ queryKey: ['posts-v2', variables.blogId] }); showSuccess('Post updated'); }, onError: (error) => { showError('Failed to update post'); console.error('Update error:', error); }, }); }; // Usage const updatePost = useUpdatePost(); const handleSave = () => { updatePost.mutate({ blogId: 123, postId: 456, data: { responses: { '101': 'value' } } }); }; ``` ### Delete Mutation ```typescript export const useDeletePost = () => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); return useMutation({ mutationFn: ({ blogId, postId }: DeleteParams) => postApi.deletePost(blogId, postId), onSuccess: (data, variables) => { // Remove from cache manually (optimistic) queryClient.setQueryData<{ rows: Post[] }>( ['posts-v2', variables.blogId], (old) => ({ ...old, rows: old?.rows.filter(row => row.S_ID !== variables.postId) || [] }) ); showSuccess('Post deleted'); }, onError: (error, variables) => { // Rollback - refetch to get accurate state queryClient.invalidateQueries({ queryKey: ['posts-v2', variables.blogId] }); showError('Failed to delete post'); }, }); }; ``` --- ## Query Configuration Best Practices ### Default Configuration ```typescript // In QueryClientProvider setup const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutes gcTime: 1000 * 60 * 10, // 10 minutes (was cacheTime) refetchOnWindowFocus: false, // Don't refetch on focus refetchOnMount: false, // Don't refetch on mount if fresh retry: 1, // Retry failed queries once }, }, }); ``` ### Per-Query Overrides ```typescript // Frequently changing data - shorter staleTime useSuspenseQuery({ queryKey: ['notifications', 'unread'], queryFn: () => notificationApi.getUnread(), staleTime: 30 * 1000, // 30 seconds }); // Rarely changing data - longer staleTime useSuspenseQuery({ queryKey: ['form', blogId, 'structure'], queryFn: () => formApi.getStructure(blogId), staleTime: 30 * 60 * 1000, // 30 minutes }); ``` --- ## Summary **Modern Data Fetching Recipe:** 1. **Create API Service**: `features/X/api/XApi.ts` using apiClient 2. **Use useSuspenseQuery**: In components wrapped by SuspenseLoader 3. **Cache-First**: Check grid cache before API call 4. **Query Keys**: Consistent naming ['entity', id] 5. **Route Format**: `/blog/route` NOT `/api/blog/route` 6. **Mutations**: invalidateQueries after success 7. **Error Handling**: onError + useMuiSnackbar 8. **Type Safety**: Type all parameters and returns **See Also:** - [component-patterns.md](component-patterns.md) - Suspense integration - [loading-and-error-states.md](loading-and-error-states.md) - SuspenseLoader usage - [complete-examples.md](complete-examples.md) - Full working examples ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/file-organization.md ================================================ # File Organization Proper file and directory structure for maintainable, scalable frontend code in the the application. --- ## features/ vs components/ Distinction ### features/ Directory **Purpose**: Domain-specific features with their own logic, API, and components **When to use:** - Feature has multiple related components - Feature has its own API endpoints - Feature has domain-specific logic - Feature has custom hooks/utilities **Examples:** - `features/posts/` - Project catalog/post management - `features/blogs/` - Blog builder and rendering - `features/auth/` - Authentication flows **Structure:** ``` features/ my-feature/ api/ myFeatureApi.ts # API service layer components/ MyFeatureMain.tsx # Main component SubComponents/ # Related components hooks/ useMyFeature.ts # Custom hooks useSuspenseMyFeature.ts # Suspense hooks helpers/ myFeatureHelpers.ts # Utility functions types/ index.ts # TypeScript types index.ts # Public exports ``` ### components/ Directory **Purpose**: Truly reusable components used across multiple features **When to use:** - Component is used in 3+ places - Component is generic (no feature-specific logic) - Component is a UI primitive or pattern **Examples:** - `components/SuspenseLoader/` - Loading wrapper - `components/CustomAppBar/` - Application header - `components/ErrorBoundary/` - Error handling - `components/LoadingOverlay/` - Loading overlay **Structure:** ``` components/ SuspenseLoader/ SuspenseLoader.tsx SuspenseLoader.test.tsx CustomAppBar/ CustomAppBar.tsx CustomAppBar.test.tsx ``` --- ## Feature Directory Structure (Detailed) ### Complete Feature Example Based on `features/posts/` structure: ``` features/ posts/ api/ postApi.ts # API service layer (GET, POST, PUT, DELETE) components/ PostTable.tsx # Main container component grids/ PostDataGrid/ PostDataGrid.tsx drawers/ ProjectPostDrawer/ ProjectPostDrawer.tsx cells/ editors/ TextEditCell.tsx renderers/ DateCell.tsx toolbar/ CustomToolbar.tsx hooks/ usePostQueries.ts # Regular queries useSuspensePost.ts # Suspense queries usePostMutations.ts # Mutations useGridLayout.ts # Feature-specific hooks helpers/ postHelpers.ts # Utility functions validation.ts # Validation logic types/ index.ts # TypeScript types/interfaces queries/ postQueries.ts # Query key factories (optional) context/ PostContext.tsx # React context (if needed) index.ts # Public API exports ``` ### Subdirectory Guidelines #### api/ Directory **Purpose**: Centralized API calls for the feature **Files:** - `{feature}Api.ts` - Main API service **Pattern:** ```typescript // features/my-feature/api/myFeatureApi.ts import apiClient from '@/lib/apiClient'; export const myFeatureApi = { getItem: async (id: number) => { const { data } = await apiClient.get(`/blog/items/${id}`); return data; }, createItem: async (payload) => { const { data } = await apiClient.post('/blog/items', payload); return data; }, }; ``` #### components/ Directory **Purpose**: Feature-specific components **Organization:** - Flat structure if <5 components - Subdirectories by responsibility if >5 components **Examples:** ``` components/ MyFeatureMain.tsx # Main component MyFeatureHeader.tsx # Supporting components MyFeatureFooter.tsx # OR with subdirectories: containers/ MyFeatureContainer.tsx presentational/ MyFeatureDisplay.tsx blogs/ MyFeatureBlog.tsx ``` #### hooks/ Directory **Purpose**: Custom hooks for the feature **Naming:** - `use` prefix (camelCase) - Descriptive of what they do **Examples:** ``` hooks/ useMyFeature.ts # Main hook useSuspenseMyFeature.ts # Suspense version useMyFeatureMutations.ts # Mutations useMyFeatureFilters.ts # Filters/search ``` #### helpers/ Directory **Purpose**: Utility functions specific to the feature **Examples:** ``` helpers/ myFeatureHelpers.ts # General utilities validation.ts # Validation logic transblogers.ts # Data transblogations constants.ts # Constants ``` #### types/ Directory **Purpose**: TypeScript types and interfaces **Files:** ``` types/ index.ts # Main types, exported internal.ts # Internal types (not exported) ``` --- ## Import Aliases (Vite Configuration) ### Available Aliases From `vite.config.ts` lines 180-185: | Alias | Resolves To | Use For | |-------|-------------|---------| | `@/` | `src/` | Absolute imports from src root | | `~types` | `src/types` | Shared TypeScript types | | `~components` | `src/components` | Reusable components | | `~features` | `src/features` | Feature imports | ### Usage Examples ```typescript // ✅ PREFERRED - Use aliases for absolute imports import { apiClient } from '@/lib/apiClient'; import { SuspenseLoader } from '~components/SuspenseLoader'; import { postApi } from '~features/posts/api/postApi'; import type { User } from '~types/user'; // ❌ AVOID - Relative paths from deep nesting import { apiClient } from '../../../lib/apiClient'; import { SuspenseLoader } from '../../../components/SuspenseLoader'; ``` ### When to Use Which Alias **@/ (General)**: - Lib utilities: `@/lib/apiClient` - Hooks: `@/hooks/useAuth` - Config: `@/config/theme` - Shared services: `@/services/authService` **~types (Type Imports)**: ```typescript import type { Post } from '~types/post'; import type { User, UserRole } from '~types/user'; ``` **~components (Reusable Components)**: ```typescript import { SuspenseLoader } from '~components/SuspenseLoader'; import { CustomAppBar } from '~components/CustomAppBar'; import { ErrorBoundary } from '~components/ErrorBoundary'; ``` **~features (Feature Imports)**: ```typescript import { postApi } from '~features/posts/api/postApi'; import { useAuth } from '~features/auth/hooks/useAuth'; ``` --- ## File Naming Conventions ### Components **Pattern**: PascalCase with `.tsx` extension ``` MyComponent.tsx PostDataGrid.tsx CustomAppBar.tsx ``` **Avoid:** - camelCase: `myComponent.tsx` ❌ - kebab-case: `my-component.tsx` ❌ - All caps: `MYCOMPONENT.tsx` ❌ ### Hooks **Pattern**: camelCase with `use` prefix, `.ts` extension ``` useMyFeature.ts useSuspensePost.ts useAuth.ts useGridLayout.ts ``` ### API Services **Pattern**: camelCase with `Api` suffix, `.ts` extension ``` myFeatureApi.ts postApi.ts userApi.ts ``` ### Helpers/Utilities **Pattern**: camelCase with descriptive name, `.ts` extension ``` myFeatureHelpers.ts validation.ts transblogers.ts constants.ts ``` ### Types **Pattern**: camelCase, `index.ts` or descriptive name ``` types/index.ts types/post.ts types/user.ts ``` --- ## When to Create a New Feature ### Create New Feature When: - Multiple related components (>3) - Has own API endpoints - Domain-specific logic - Will grow over time - Reused across multiple routes **Example:** `features/posts/` - 20+ components - Own API service - Complex state management - Used in multiple routes ### Add to Existing Feature When: - Related to existing feature - Shares same API - Logically grouped - Extends existing functionality **Example:** Adding export dialog to posts feature ### Create Reusable Component When: - Used across 3+ features - Generic, no domain logic - Pure presentation - Shared pattern **Example:** `components/SuspenseLoader/` --- ## Import Organization ### Import Order (Recommended) ```typescript // 1. React and React-related import React, { useState, useCallback, useMemo } from 'react'; import { lazy } from 'react'; // 2. Third-party libraries (alphabetical) import { Box, Paper, Button, Grid } from '@mui/material'; import type { SxProps, Theme } from '@mui/material'; import { useSuspenseQuery, useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; // 3. Alias imports (@ first, then ~) import { apiClient } from '@/lib/apiClient'; import { useAuth } from '@/hooks/useAuth'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; import { SuspenseLoader } from '~components/SuspenseLoader'; import { postApi } from '~features/posts/api/postApi'; // 4. Type imports (grouped) import type { Post } from '~types/post'; import type { User } from '~types/user'; // 5. Relative imports (same feature) import { MySubComponent } from './MySubComponent'; import { useMyFeature } from '../hooks/useMyFeature'; import { myFeatureHelpers } from '../helpers/myFeatureHelpers'; ``` **Use single quotes** for all imports (project standard) --- ## Public API Pattern ### feature/index.ts Export public API from feature for clean imports: ```typescript // features/my-feature/index.ts // Export main components export { MyFeatureMain } from './components/MyFeatureMain'; export { MyFeatureHeader } from './components/MyFeatureHeader'; // Export hooks export { useMyFeature } from './hooks/useMyFeature'; export { useSuspenseMyFeature } from './hooks/useSuspenseMyFeature'; // Export API export { myFeatureApi } from './api/myFeatureApi'; // Export types export type { MyFeatureData, MyFeatureConfig } from './types'; ``` **Usage:** ```typescript // ✅ Clean import from feature index import { MyFeatureMain, useMyFeature } from '~features/my-feature'; // ❌ Avoid deep imports (but OK if needed) import { MyFeatureMain } from '~features/my-feature/components/MyFeatureMain'; ``` --- ## Directory Structure Visualization ``` src/ ├── features/ # Domain-specific features │ ├── posts/ │ │ ├── api/ │ │ ├── components/ │ │ ├── hooks/ │ │ ├── helpers/ │ │ ├── types/ │ │ └── index.ts │ ├── blogs/ │ └── auth/ │ ├── components/ # Reusable components │ ├── SuspenseLoader/ │ ├── CustomAppBar/ │ ├── ErrorBoundary/ │ └── LoadingOverlay/ │ ├── routes/ # TanStack Router routes │ ├── __root.tsx │ ├── index.tsx │ ├── project-catalog/ │ │ ├── index.tsx │ │ └── create/ │ └── blogs/ │ ├── hooks/ # Shared hooks │ ├── useAuth.ts │ ├── useMuiSnackbar.ts │ └── useDebounce.ts │ ├── lib/ # Shared utilities │ ├── apiClient.ts │ └── utils.ts │ ├── types/ # Shared TypeScript types │ ├── user.ts │ ├── post.ts │ └── common.ts │ ├── config/ # Configuration │ └── theme.ts │ └── App.tsx # Root component ``` --- ## Summary **Key Principles:** 1. **features/** for domain-specific code 2. **components/** for truly reusable UI 3. Use subdirectories: api/, components/, hooks/, helpers/, types/ 4. Import aliases for clean imports (@/, ~types, ~components, ~features) 5. Consistent naming: PascalCase components, camelCase utilities 6. Export public API from feature index.ts **See Also:** - [component-patterns.md](component-patterns.md) - Component structure - [data-fetching.md](data-fetching.md) - API service patterns - [complete-examples.md](complete-examples.md) - Full feature example ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/loading-and-error-states.md ================================================ # Loading & Error States **CRITICAL**: Proper loading and error state handling prevents layout shift and provides better user experience. --- ## ⚠️ CRITICAL RULE: Never Use Early Returns ### The Problem ```typescript // ❌ NEVER DO THIS - Early return with loading spinner const Component = () => { const { data, isLoading } = useQuery(); // WRONG: This causes layout shift and poor UX if (isLoading) { return ; } return ; }; ``` **Why this is bad:** 1. **Layout Shift**: Content position jumps when loading completes 2. **CLS (Cumulative Layout Shift)**: Poor Core Web Vital score 3. **Jarring UX**: Page structure changes suddenly 4. **Lost Scroll Position**: User loses place on page ### The Solutions **Option 1: SuspenseLoader (PREFERRED for new components)** ```typescript import { SuspenseLoader } from '~components/SuspenseLoader'; const HeavyComponent = React.lazy(() => import('./HeavyComponent')); export const MyComponent: React.FC = () => { return ( ); }; ``` **Option 2: LoadingOverlay (for legacy useQuery patterns)** ```typescript import { LoadingOverlay } from '~components/LoadingOverlay'; export const MyComponent: React.FC = () => { const { data, isLoading } = useQuery({ ... }); return ( ); }; ``` --- ## SuspenseLoader Component ### What It Does - Shows loading indicator while lazy components load - Smooth fade-in animation - Prevents layout shift - Consistent loading experience across app ### Import ```typescript import { SuspenseLoader } from '~components/SuspenseLoader'; // Or import { SuspenseLoader } from '@/components/SuspenseLoader'; ``` ### Basic Usage ```typescript ``` ### With useSuspenseQuery ```typescript import { useSuspenseQuery } from '@tanstack/react-query'; import { SuspenseLoader } from '~components/SuspenseLoader'; const Inner: React.FC = () => { // No isLoading needed! const { data } = useSuspenseQuery({ queryKey: ['data'], queryFn: () => api.getData(), }); return ; }; // Outer component wraps in Suspense export const Outer: React.FC = () => { return ( ); }; ``` ### Multiple Suspense Boundaries **Pattern**: Separate loading for independent sections ```typescript export const Dashboard: React.FC = () => { return (
); }; ``` **Benefits:** - Each section loads independently - User sees partial content sooner - Better perceived performance ### Nested Suspense ```typescript export const ParentComponent: React.FC = () => { return ( {/* Parent suspends while loading */} {/* Nested suspense for child */} ); }; ``` --- ## LoadingOverlay Component ### When to Use - Legacy components with `useQuery` (not refactored to Suspense yet) - Overlay loading state needed - Can't use Suspense boundaries ### Usage ```typescript import { LoadingOverlay } from '~components/LoadingOverlay'; export const MyComponent: React.FC = () => { const { data, isLoading } = useQuery({ queryKey: ['data'], queryFn: () => api.getData(), }); return ( {data && } ); }; ``` **What it does:** - Shows semi-transparent overlay with spinner - Content area reserved (no layout shift) - Prevents interaction while loading --- ## Error Handling ### useMuiSnackbar Hook (REQUIRED) **NEVER use react-toastify** - Project standard is MUI Snackbar ```typescript import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; export const MyComponent: React.FC = () => { const { showSuccess, showError, showInfo, showWarning } = useMuiSnackbar(); const handleAction = async () => { try { await api.doSomething(); showSuccess('Operation completed successfully'); } catch (error) { showError('Operation failed'); } }; return ; }; ``` **Available Methods:** - `showSuccess(message)` - Green success message - `showError(message)` - Red error message - `showWarning(message)` - Orange warning message - `showInfo(message)` - Blue info message ### TanStack Query Error Callbacks ```typescript import { useSuspenseQuery } from '@tanstack/react-query'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; export const MyComponent: React.FC = () => { const { showError } = useMuiSnackbar(); const { data } = useSuspenseQuery({ queryKey: ['data'], queryFn: () => api.getData(), // Handle errors onError: (error) => { showError('Failed to load data'); console.error('Query error:', error); }, }); return ; }; ``` ### Error Boundaries ```typescript import { ErrorBoundary } from 'react-error-boundary'; function ErrorFallback({ error, resetErrorBoundary }) { return ( Something went wrong {error.message} ); } export const MyPage: React.FC = () => { return ( console.error('Boundary caught:', error)} > ); }; ``` --- ## Complete Examples ### Example 1: Modern Component with Suspense ```typescript import React from 'react'; import { Box, Paper } from '@mui/material'; import { useSuspenseQuery } from '@tanstack/react-query'; import { SuspenseLoader } from '~components/SuspenseLoader'; import { myFeatureApi } from '../api/myFeatureApi'; // Inner component uses useSuspenseQuery const InnerComponent: React.FC<{ id: number }> = ({ id }) => { const { data } = useSuspenseQuery({ queryKey: ['entity', id], queryFn: () => myFeatureApi.getEntity(id), }); // data is always defined - no isLoading needed! return (

{data.title}

{data.description}

); }; // Outer component provides Suspense boundary export const OuterComponent: React.FC<{ id: number }> = ({ id }) => { return ( ); }; export default OuterComponent; ``` ### Example 2: Legacy Pattern with LoadingOverlay ```typescript import React from 'react'; import { Box } from '@mui/material'; import { useQuery } from '@tanstack/react-query'; import { LoadingOverlay } from '~components/LoadingOverlay'; import { myFeatureApi } from '../api/myFeatureApi'; export const LegacyComponent: React.FC<{ id: number }> = ({ id }) => { const { data, isLoading, error } = useQuery({ queryKey: ['entity', id], queryFn: () => myFeatureApi.getEntity(id), }); return ( {error && } {data && } ); }; ``` ### Example 3: Error Handling with Snackbar ```typescript import React from 'react'; import { useSuspenseQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Button } from '@mui/material'; import { useMuiSnackbar } from '@/hooks/useMuiSnackbar'; import { myFeatureApi } from '../api/myFeatureApi'; export const EntityEditor: React.FC<{ id: number }> = ({ id }) => { const queryClient = useQueryClient(); const { showSuccess, showError } = useMuiSnackbar(); const { data } = useSuspenseQuery({ queryKey: ['entity', id], queryFn: () => myFeatureApi.getEntity(id), onError: () => { showError('Failed to load entity'); }, }); const updateMutation = useMutation({ mutationFn: (updates) => myFeatureApi.update(id, updates), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['entity', id] }); showSuccess('Entity updated successfully'); }, onError: () => { showError('Failed to update entity'); }, }); return ( ); }; ``` --- ## Loading State Anti-Patterns ### ❌ What NOT to Do ```typescript // ❌ NEVER - Early return if (isLoading) { return ; } // ❌ NEVER - Conditional rendering {isLoading ? : } // ❌ NEVER - Layout changes if (isLoading) { return ( ); } return ( // Different height! ); ``` ### ✅ What TO Do ```typescript // ✅ BEST - useSuspenseQuery + SuspenseLoader // ✅ ACCEPTABLE - LoadingOverlay // ✅ OK - Inline skeleton with same layout {isLoading ? : } ``` --- ## Skeleton Loading (Alternative) ### MUI Skeleton Component ```typescript import { Skeleton, Box } from '@mui/material'; export const MyComponent: React.FC = () => { const { data, isLoading } = useQuery({ ... }); return ( {isLoading ? ( <> ) : ( <> {data.title} {data.description} )} ); }; ``` **Key**: Skeleton must have **same layout** as actual content (no shift) --- ## Summary **Loading States:** - ✅ **PREFERRED**: SuspenseLoader + useSuspenseQuery (modern pattern) - ✅ **ACCEPTABLE**: LoadingOverlay (legacy pattern) - ✅ **OK**: Skeleton with same layout - ❌ **NEVER**: Early returns or conditional layout **Error Handling:** - ✅ **ALWAYS**: useMuiSnackbar for user feedback - ❌ **NEVER**: react-toastify - ✅ Use onError callbacks in queries/mutations - ✅ Error boundaries for component-level errors **See Also:** - [component-patterns.md](component-patterns.md) - Suspense integration - [data-fetching.md](data-fetching.md) - useSuspenseQuery details ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/performance.md ================================================ # Performance Optimization Patterns for optimizing React component performance, preventing unnecessary re-renders, and avoiding memory leaks. --- ## Memoization Patterns ### useMemo for Expensive Computations ```typescript import { useMemo } from 'react'; export const DataDisplay: React.FC<{ items: Item[], searchTerm: string }> = ({ items, searchTerm, }) => { // ❌ AVOID - Runs on every render const filteredItems = items .filter(item => item.name.includes(searchTerm)) .sort((a, b) => a.name.localeCompare(b.name)); // ✅ CORRECT - Memoized, only recalculates when dependencies change const filteredItems = useMemo(() => { return items .filter(item => item.name.toLowerCase().includes(searchTerm.toLowerCase())) .sort((a, b) => a.name.localeCompare(b.name)); }, [items, searchTerm]); return ; }; ``` **When to use useMemo:** - Filtering/sorting large arrays - Complex calculations - Transforming data structures - Expensive computations (loops, recursion) **When NOT to use useMemo:** - Simple string concatenation - Basic arithmetic - Premature optimization (profile first!) --- ## useCallback for Event Handlers ### The Problem ```typescript // ❌ AVOID - Creates new function on every render export const Parent: React.FC = () => { const handleClick = (id: string) => { console.log('Clicked:', id); }; // Child re-renders every time Parent renders // because handleClick is a new function reference each time return ; }; ``` ### The Solution ```typescript import { useCallback } from 'react'; export const Parent: React.FC = () => { // ✅ CORRECT - Stable function reference const handleClick = useCallback((id: string) => { console.log('Clicked:', id); }, []); // Empty deps = function never changes // Child only re-renders when props actually change return ; }; ``` **When to use useCallback:** - Functions passed as props to children - Functions used as dependencies in useEffect - Functions passed to memoized components - Event handlers in lists **When NOT to use useCallback:** - Event handlers not passed to children - Simple inline handlers: `onClick={() => doSomething()}` --- ## React.memo for Component Memoization ### Basic Usage ```typescript import React from 'react'; interface ExpensiveComponentProps { data: ComplexData; onAction: () => void; } // ✅ Wrap expensive components in React.memo export const ExpensiveComponent = React.memo( function ExpensiveComponent({ data, onAction }) { // Complex rendering logic return ; } ); ``` **When to use React.memo:** - Component renders frequently - Component has expensive rendering - Props don't change often - Component is a list item - DataGrid cells/renderers **When NOT to use React.memo:** - Props change frequently anyway - Rendering is already fast - Premature optimization --- ## Debounced Search ### Using use-debounce Hook ```typescript import { useState } from 'react'; import { useDebounce } from 'use-debounce'; import { useSuspenseQuery } from '@tanstack/react-query'; export const SearchComponent: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); // Debounce for 300ms const [debouncedSearchTerm] = useDebounce(searchTerm, 300); // Query uses debounced value const { data } = useSuspenseQuery({ queryKey: ['search', debouncedSearchTerm], queryFn: () => api.search(debouncedSearchTerm), enabled: debouncedSearchTerm.length > 0, }); return ( setSearchTerm(e.target.value)} placeholder='Search...' /> ); }; ``` **Optimal Debounce Timing:** - **300-500ms**: Search/filtering - **1000ms**: Auto-save - **100-200ms**: Real-time validation --- ## Memory Leak Prevention ### Cleanup Timeouts/Intervals ```typescript import { useEffect, useState } from 'react'; export const MyComponent: React.FC = () => { const [count, setCount] = useState(0); useEffect(() => { // ✅ CORRECT - Cleanup interval const intervalId = setInterval(() => { setCount(c => c + 1); }, 1000); return () => { clearInterval(intervalId); // Cleanup! }; }, []); useEffect(() => { // ✅ CORRECT - Cleanup timeout const timeoutId = setTimeout(() => { console.log('Delayed action'); }, 5000); return () => { clearTimeout(timeoutId); // Cleanup! }; }, []); return
{count}
; }; ``` ### Cleanup Event Listeners ```typescript useEffect(() => { const handleResize = () => { console.log('Resized'); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); // Cleanup! }; }, []); ``` ### Abort Controllers for Fetch ```typescript useEffect(() => { const abortController = new AbortController(); fetch('/api/data', { signal: abortController.signal }) .then(response => response.json()) .then(data => setState(data)) .catch(error => { if (error.name === 'AbortError') { console.log('Fetch aborted'); } }); return () => { abortController.abort(); // Cleanup! }; }, []); ``` **Note**: With TanStack Query, this is handled automatically. --- ## Form Performance ### Watch Specific Fields (Not All) ```typescript import { useForm } from 'react-hook-form'; export const MyForm: React.FC = () => { const { register, watch, handleSubmit } = useForm(); // ❌ AVOID - Watches all fields, re-renders on any change const formValues = watch(); // ✅ CORRECT - Watch only what you need const username = watch('username'); const email = watch('email'); // Or multiple specific fields const [username, email] = watch(['username', 'email']); return (
{/* Only re-renders when username/email change */}

Username: {username}, Email: {email}

); }; ``` --- ## List Rendering Optimization ### Key Prop Usage ```typescript // ✅ CORRECT - Stable unique keys {items.map(item => ( {item.name} ))} // ❌ AVOID - Index as key (unstable if list changes) {items.map((item, index) => ( // WRONG if list reorders {item.name} ))} ``` ### Memoized List Items ```typescript const ListItem = React.memo(({ item, onAction }) => { return ( onAction(item.id)}> {item.name} ); }); export const List: React.FC<{ items: Item[] }> = ({ items }) => { const handleAction = useCallback((id: string) => { console.log('Action:', id); }, []); return ( {items.map(item => ( ))} ); }; ``` --- ## Preventing Component Re-initialization ### The Problem ```typescript // ❌ AVOID - Component recreated on every render export const Parent: React.FC = () => { // New component definition each render! const ChildComponent = () =>
Child
; return ; // Unmounts and remounts every render }; ``` ### The Solution ```typescript // ✅ CORRECT - Define outside or use useMemo const ChildComponent: React.FC = () =>
Child
; export const Parent: React.FC = () => { return ; // Stable component }; // ✅ OR if dynamic, use useMemo export const Parent: React.FC<{ config: Config }> = ({ config }) => { const DynamicComponent = useMemo(() => { return () =>
{config.title}
; }, [config.title]); return ; }; ``` --- ## Lazy Loading Heavy Dependencies ### Code Splitting ```typescript // ❌ AVOID - Import heavy libraries at top level import jsPDF from 'jspdf'; // Large library loaded immediately import * as XLSX from 'xlsx'; // Large library loaded immediately // ✅ CORRECT - Dynamic import when needed const handleExportPDF = async () => { const { jsPDF } = await import('jspdf'); const doc = new jsPDF(); // Use it }; const handleExportExcel = async () => { const XLSX = await import('xlsx'); // Use it }; ``` --- ## Summary **Performance Checklist:** - ✅ `useMemo` for expensive computations (filter, sort, map) - ✅ `useCallback` for functions passed to children - ✅ `React.memo` for expensive components - ✅ Debounce search/filter (300-500ms) - ✅ Cleanup timeouts/intervals in useEffect - ✅ Watch specific form fields (not all) - ✅ Stable keys in lists - ✅ Lazy load heavy libraries - ✅ Code splitting with React.lazy **See Also:** - [component-patterns.md](component-patterns.md) - Lazy loading - [data-fetching.md](data-fetching.md) - TanStack Query optimization - [complete-examples.md](complete-examples.md) - Performance patterns in context ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/routing-guide.md ================================================ # Routing Guide TanStack Router implementation with folder-based routing and lazy loading patterns. --- ## TanStack Router Overview **TanStack Router** with file-based routing: - Folder structure defines routes - Lazy loading for code splitting - Type-safe routing - Breadcrumb loaders --- ## Folder-Based Routing ### Directory Structure ``` routes/ __root.tsx # Root layout index.tsx # Home route (/) posts/ index.tsx # /posts create/ index.tsx # /posts/create $postId.tsx # /posts/:postId (dynamic) comments/ index.tsx # /comments ``` **Pattern**: - `index.tsx` = Route at that path - `$param.tsx` = Dynamic parameter - Nested folders = Nested routes --- ## Basic Route Pattern ### Example from posts/index.tsx ```typescript /** * Posts route component * Displays the main blog posts list */ import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; // Lazy load the page component const PostsList = lazy(() => import('@/features/posts/components/PostsList').then( (module) => ({ default: module.PostsList }), ), ); export const Route = createFileRoute('/posts/')({ component: PostsPage, // Define breadcrumb data loader: () => ({ crumb: 'Posts', }), }); function PostsPage() { return ( ); } export default PostsPage; ``` **Key Points:** - Lazy load heavy components - `createFileRoute` with route path - `loader` for breadcrumb data - Page component renders content - Export both Route and component --- ## Lazy Loading Routes ### Named Export Pattern ```typescript import { lazy } from 'react'; // For named exports, use .then() to map to default const MyPage = lazy(() => import('@/features/my-feature/components/MyPage').then( (module) => ({ default: module.MyPage }) ) ); ``` ### Default Export Pattern ```typescript import { lazy } from 'react'; // For default exports, simpler syntax const MyPage = lazy(() => import('@/features/my-feature/components/MyPage')); ``` ### Why Lazy Load Routes? - Code splitting - smaller initial bundle - Faster initial page load - Load route code only when navigated to - Better performance --- ## createFileRoute ### Basic Configuration ```typescript export const Route = createFileRoute('/my-route/')({ component: MyRoutePage, }); function MyRoutePage() { return
My Route Content
; } ``` ### With Breadcrumb Loader ```typescript export const Route = createFileRoute('/my-route/')({ component: MyRoutePage, loader: () => ({ crumb: 'My Route Title', }), }); ``` Breadcrumb appears in navigation/app bar automatically. ### With Data Loader ```typescript export const Route = createFileRoute('/my-route/')({ component: MyRoutePage, loader: async () => { // Can prefetch data here const data = await api.getData(); return { crumb: 'My Route', data }; }, }); ``` ### With Search Params ```typescript export const Route = createFileRoute('/search/')({ component: SearchPage, validateSearch: (search: Record) => { return { query: (search.query as string) || '', page: Number(search.page) || 1, }; }, }); function SearchPage() { const { query, page } = Route.useSearch(); // Use query and page } ``` --- ## Dynamic Routes ### Parameter Routes ```typescript // routes/users/$userId.tsx export const Route = createFileRoute('/users/$userId')({ component: UserPage, }); function UserPage() { const { userId } = Route.useParams(); return ; } ``` ### Multiple Parameters ```typescript // routes/posts/$postId/comments/$commentId.tsx export const Route = createFileRoute('/posts/$postId/comments/$commentId')({ component: CommentPage, }); function CommentPage() { const { postId, commentId } = Route.useParams(); return ; } ``` --- ## Navigation ### Programmatic Navigation ```typescript import { useNavigate } from '@tanstack/react-router'; export const MyComponent: React.FC = () => { const navigate = useNavigate(); const handleClick = () => { navigate({ to: '/posts' }); }; return ; }; ``` ### With Parameters ```typescript const handleNavigate = () => { navigate({ to: '/users/$userId', params: { userId: '123' }, }); }; ``` ### With Search Params ```typescript const handleSearch = () => { navigate({ to: '/search', search: { query: 'test', page: 1 }, }); }; ``` --- ## Route Layout Pattern ### Root Layout (__root.tsx) ```typescript import { createRootRoute, Outlet } from '@tanstack/react-router'; import { Box } from '@mui/material'; import { CustomAppBar } from '~components/CustomAppBar'; export const Route = createRootRoute({ component: RootLayout, }); function RootLayout() { return ( {/* Child routes render here */} ); } ``` ### Nested Layouts ```typescript // routes/dashboard/index.tsx export const Route = createFileRoute('/dashboard/')({ component: DashboardLayout, }); function DashboardLayout() { return ( {/* Nested routes */} ); } ``` --- ## Complete Route Example ```typescript /** * User profile route * Path: /users/:userId */ import { createFileRoute } from '@tanstack/react-router'; import { lazy } from 'react'; import { SuspenseLoader } from '~components/SuspenseLoader'; // Lazy load heavy component const UserProfile = lazy(() => import('@/features/users/components/UserProfile').then( (module) => ({ default: module.UserProfile }) ) ); export const Route = createFileRoute('/users/$userId')({ component: UserPage, loader: () => ({ crumb: 'User Profile', }), }); function UserPage() { const { userId } = Route.useParams(); return ( ); } export default UserPage; ``` --- ## Summary **Routing Checklist:** - ✅ Folder-based: `routes/my-route/index.tsx` - ✅ Lazy load components: `React.lazy(() => import())` - ✅ Use `createFileRoute` with route path - ✅ Add breadcrumb in `loader` function - ✅ Wrap in `SuspenseLoader` for loading states - ✅ Use `Route.useParams()` for dynamic params - ✅ Use `useNavigate()` for programmatic navigation **See Also:** - [component-patterns.md](component-patterns.md) - Lazy loading patterns - [loading-and-error-states.md](loading-and-error-states.md) - SuspenseLoader usage - [complete-examples.md](complete-examples.md) - Full route examples ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/styling-guide.md ================================================ # Styling Guide Modern styling patterns for using MUI v7 sx prop, inline styles, and theme integration. --- ## Inline vs Separate Styles ### Decision Threshold **<100 lines: Inline styles at top of component** ```typescript import type { SxProps, Theme } from '@mui/material'; const componentStyles: Record> = { container: { p: 2, display: 'flex', flexDirection: 'column', }, header: { mb: 2, borderBottom: '1px solid', borderColor: 'divider', }, // ... more styles }; export const MyComponent: React.FC = () => { return (

Title

); }; ``` **>100 lines: Separate `.styles.ts` file** ```typescript // MyComponent.styles.ts import type { SxProps, Theme } from '@mui/material'; export const componentStyles: Record> = { container: { ... }, header: { ... }, // ... 100+ lines of styles }; // MyComponent.tsx import { componentStyles } from './MyComponent.styles'; export const MyComponent: React.FC = () => { return ...; }; ``` ### Real Example: UnifiedForm.tsx **Lines 48-126**: 78 lines of inline styles (acceptable) ```typescript const formStyles: Record> = { gridContainer: { height: '100%', maxHeight: 'calc(100vh - 220px)', }, section: { height: '100%', maxHeight: 'calc(100vh - 220px)', overflow: 'auto', p: 4, }, // ... 15 more style objects }; ``` **Guideline**: User is comfortable with ~80 lines inline. Use your judgment around 100 lines. --- ## sx Prop Patterns ### Basic Usage ```typescript Content ``` ### With Theme Access ```typescript theme.palette.primary.main, color: (theme) => theme.palette.primary.contrastText, borderRadius: (theme) => theme.shape.borderRadius, }} > Themed Box ``` ### Responsive Styles ```typescript Responsive Layout ``` ### Pseudo-Selectors ```typescript Interactive Box ``` --- ## MUI v7 Patterns ### Grid Component (v7 Syntax) ```typescript import { Grid } from '@mui/material'; // ✅ CORRECT - v7 syntax with size prop Left Column Right Column // ❌ WRONG - Old v6 syntax {/* OLD - Don't use */} Content ``` **Key Change**: `size={{ xs: 12, md: 6 }}` instead of `xs={12} md={6}` ### Responsive Grid ```typescript Responsive Column ``` ### Nested Grids ```typescript Nested 1 Nested 2 Sidebar ``` --- ## Type-Safe Styles ### Style Object Type ```typescript import type { SxProps, Theme } from '@mui/material'; // Type-safe styles const styles: Record> = { container: { p: 2, // Autocomplete and type checking work here }, }; // Or individual style const containerStyle: SxProps = { p: 2, display: 'flex', }; ``` ### Theme-Aware Styles ```typescript const styles: Record> = { primary: { color: (theme) => theme.palette.primary.main, backgroundColor: (theme) => theme.palette.primary.light, '&:hover': { backgroundColor: (theme) => theme.palette.primary.dark, }, }, customSpacing: { padding: (theme) => theme.spacing(2), margin: (theme) => theme.spacing(1, 2), // top/bottom: 1, left/right: 2 }, }; ``` --- ## What NOT to Use ### ❌ makeStyles (MUI v4 pattern) ```typescript // ❌ AVOID - Old Material-UI v4 pattern import { makeStyles } from '@mui/styles'; const useStyles = makeStyles((theme) => ({ root: { padding: theme.spacing(2), }, })); ``` **Why avoid**: Deprecated, v7 doesn't support it well ### ❌ styled() Components ```typescript // ❌ AVOID - styled-components pattern import { styled } from '@mui/material/styles'; const StyledBox = styled(Box)(({ theme }) => ({ padding: theme.spacing(2), })); ``` **Why avoid**: sx prop is more flexible and doesn't create new components ### ✅ Use sx Prop Instead ```typescript // ✅ PREFERRED Content ``` --- ## Code Style Standards ### Indentation **4 spaces** (not 2, not tabs) ```typescript const styles: Record> = { container: { p: 2, display: 'flex', flexDirection: 'column', }, }; ``` ### Quotes **Single quotes** for strings (project standard) ```typescript // ✅ CORRECT const color = 'primary.main'; import { Box } from '@mui/material'; // ❌ WRONG const color = "primary.main"; import { Box } from "@mui/material"; ``` ### Trailing Commas **Always use trailing commas** in objects and arrays ```typescript // ✅ CORRECT const styles = { container: { p: 2 }, header: { mb: 1 }, // Trailing comma }; const items = [ 'item1', 'item2', // Trailing comma ]; // ❌ WRONG - No trailing comma const styles = { container: { p: 2 }, header: { mb: 1 } // Missing comma }; ``` --- ## Common Style Patterns ### Flexbox Layout ```typescript const styles = { flexRow: { display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2, }, flexColumn: { display: 'flex', flexDirection: 'column', gap: 1, }, spaceBetween: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', }, }; ``` ### Spacing ```typescript // Padding p: 2 // All sides px: 2 // Horizontal (left + right) py: 2 // Vertical (top + bottom) pt: 2, pr: 1 // Specific sides // Margin m: 2, mx: 2, my: 2, mt: 2, mr: 1 // Units: 1 = 8px (theme.spacing(1)) p: 2 // = 16px p: 0.5 // = 4px ``` ### Positioning ```typescript const styles = { relative: { position: 'relative', }, absolute: { position: 'absolute', top: 0, right: 0, }, sticky: { position: 'sticky', top: 0, zIndex: 1000, }, }; ``` --- ## Summary **Styling Checklist:** - ✅ Use `sx` prop for MUI styling - ✅ Type-safe with `SxProps` - ✅ <100 lines: inline; >100 lines: separate file - ✅ MUI v7 Grid: `size={{ xs: 12 }}` - ✅ 4 space indentation - ✅ Single quotes - ✅ Trailing commas - ❌ No makeStyles or styled() **See Also:** - [component-patterns.md](component-patterns.md) - Component structure - [complete-examples.md](complete-examples.md) - Full styling examples ================================================ FILE: .claude/skills/frontend-dev-guidelines/resources/typescript-standards.md ================================================ # TypeScript Standards TypeScript best practices for type safety and maintainability in React frontend code. --- ## Strict Mode ### Configuration TypeScript strict mode is **enabled** in the project: ```json // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true } } ``` **This means:** - No implicit `any` types - Null/undefined must be handled explicitly - Type safety enforced --- ## No `any` Type ### The Rule ```typescript // ❌ NEVER use any function handleData(data: any) { return data.something; } // ✅ Use specific types interface MyData { something: string; } function handleData(data: MyData) { return data.something; } // ✅ Or use unknown for truly unknown data function handleUnknown(data: unknown) { if (typeof data === 'object' && data !== null && 'something' in data) { return (data as MyData).something; } } ``` **If you truly don't know the type:** - Use `unknown` (forces type checking) - Use type guards to narrow - Document why type is unknown --- ## Explicit Return Types ### Function Return Types ```typescript // ✅ CORRECT - Explicit return type function getUser(id: number): Promise { return apiClient.get(`/users/${id}`); } function calculateTotal(items: Item[]): number { return items.reduce((sum, item) => sum + item.price, 0); } // ❌ AVOID - Implicit return type (less clear) function getUser(id: number) { return apiClient.get(`/users/${id}`); } ``` ### Component Return Types ```typescript // React.FC already provides return type (ReactElement) export const MyComponent: React.FC = ({ prop }) => { return
{prop}
; }; // For custom hooks function useMyData(id: number): { data: Data; isLoading: boolean } { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); return { data: data!, isLoading }; } ``` --- ## Type Imports ### Use 'type' Keyword ```typescript // ✅ CORRECT - Explicitly mark as type import import type { User } from '~types/user'; import type { Post } from '~types/post'; import type { SxProps, Theme } from '@mui/material'; // ❌ AVOID - Mixed value and type imports import { User } from '~types/user'; // Unclear if type or value ``` **Benefits:** - Clearly separates types from values - Better tree-shaking - Prevents circular dependencies - TypeScript compiler optimization --- ## Component Prop Interfaces ### Interface Pattern ```typescript /** * Props for MyComponent */ interface MyComponentProps { /** The user ID to display */ userId: number; /** Optional callback when action completes */ onComplete?: () => void; /** Display mode for the component */ mode?: 'view' | 'edit'; /** Additional CSS classes */ className?: string; } export const MyComponent: React.FC = ({ userId, onComplete, mode = 'view', // Default value className, }) => { return
...
; }; ``` **Key Points:** - Separate interface for props - JSDoc comments for each prop - Optional props use `?` - Provide defaults in destructuring ### Props with Children ```typescript interface ContainerProps { children: React.ReactNode; title: string; } // React.FC automatically includes children type, but be explicit export const Container: React.FC = ({ children, title }) => { return (

{title}

{children}
); }; ``` --- ## Utility Types ### Partial ```typescript // Make all properties optional type UserUpdate = Partial; function updateUser(id: number, updates: Partial) { // updates can have any subset of User properties } ``` ### Pick ```typescript // Select specific properties type UserPreview = Pick; const preview: UserPreview = { id: 1, name: 'John', email: 'john@example.com', // Other User properties not allowed }; ``` ### Omit ```typescript // Exclude specific properties type UserWithoutPassword = Omit; const publicUser: UserWithoutPassword = { id: 1, name: 'John', email: 'john@example.com', // password and passwordHash not allowed }; ``` ### Required ```typescript // Make all properties required type RequiredConfig = Required; // All optional props become required ``` ### Record ```typescript // Type-safe object/map const userMap: Record = { 'user1': { id: 1, name: 'John' }, 'user2': { id: 2, name: 'Jane' }, }; // For styles import type { SxProps, Theme } from '@mui/material'; const styles: Record> = { container: { p: 2 }, header: { mb: 1 }, }; ``` --- ## Type Guards ### Basic Type Guards ```typescript function isUser(data: unknown): data is User { return ( typeof data === 'object' && data !== null && 'id' in data && 'name' in data ); } // Usage if (isUser(response)) { console.log(response.name); // TypeScript knows it's User } ``` ### Discriminated Unions ```typescript type LoadingState = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: Data } | { status: 'error'; error: Error }; function Component({ state }: { state: LoadingState }) { // TypeScript narrows type based on status if (state.status === 'success') { return ; // data available here } if (state.status === 'error') { return ; // error available here } return ; } ``` --- ## Generic Types ### Generic Functions ```typescript function getById(items: T[], id: number): T | undefined { return items.find(item => (item as any).id === id); } // Usage with type inference const users: User[] = [...]; const user = getById(users, 123); // Type: User | undefined ``` ### Generic Components ```typescript interface ListProps { items: T[]; renderItem: (item: T) => React.ReactNode; } export function List({ items, renderItem }: ListProps): React.ReactElement { return (
{items.map((item, index) => (
{renderItem(item)}
))}
); } // Usage items={users} renderItem={(user) => } /> ``` --- ## Type Assertions (Use Sparingly) ### When to Use ```typescript // ✅ OK - When you know more than TypeScript const element = document.getElementById('my-element') as HTMLInputElement; const value = element.value; // ✅ OK - API response that you've validated const response = await api.getData(); const user = response.data as User; // You know the shape ``` ### When NOT to Use ```typescript // ❌ AVOID - Circumventing type safety const data = getData() as any; // WRONG - defeats TypeScript // ❌ AVOID - Unsafe assertion const value = unknownValue as string; // Might not actually be string ``` --- ## Null/Undefined Handling ### Optional Chaining ```typescript // ✅ CORRECT const name = user?.profile?.name; // Equivalent to: const name = user && user.profile && user.profile.name; ``` ### Nullish Coalescing ```typescript // ✅ CORRECT const displayName = user?.name ?? 'Anonymous'; // Only uses default if null or undefined // (Different from || which triggers on '', 0, false) ``` ### Non-Null Assertion (Use Carefully) ```typescript // ✅ OK - When you're certain value exists const data = queryClient.getQueryData(['data'])!; // ⚠️ CAREFUL - Only use when you KNOW it's not null // Better to check explicitly: const data = queryClient.getQueryData(['data']); if (data) { // Use data } ``` --- ## Summary **TypeScript Checklist:** - ✅ Strict mode enabled - ✅ No `any` type (use `unknown` if needed) - ✅ Explicit return types on functions - ✅ Use `import type` for type imports - ✅ JSDoc comments on prop interfaces - ✅ Utility types (Partial, Pick, Omit, Required, Record) - ✅ Type guards for narrowing - ✅ Optional chaining and nullish coalescing - ❌ Avoid type assertions unless necessary **See Also:** - [component-patterns.md](component-patterns.md) - Component typing - [data-fetching.md](data-fetching.md) - API typing ================================================ FILE: .claude/skills/route-tester/SKILL.md ================================================ --- name: route-tester description: Test authenticated routes in the your project using cookie-based authentication. Use this skill when testing API endpoints, validating route functionality, or debugging authentication issues. Includes patterns for using test-auth-route.js and mock authentication. --- # your project Route Tester Skill ## Purpose This skill provides patterns for testing authenticated routes in the your project using cookie-based JWT authentication. ## When to Use This Skill - Testing new API endpoints - Validating route functionality after changes - Debugging authentication issues - Testing POST/PUT/DELETE operations - Verifying request/response data ## your project Authentication Overview The your project uses: - **Keycloak** for SSO (realm: yourRealm) - **Cookie-based JWT** tokens (not Bearer headers) - **Cookie name**: `refresh_token` - **JWT signing**: Using secret from `config.ini` ## Testing Methods ### Method 1: test-auth-route.js (RECOMMENDED) The `test-auth-route.js` script handles all authentication complexity automatically. **Location**: `/root/git/your project_pre/scripts/test-auth-route.js` #### Basic GET Request ```bash node scripts/test-auth-route.js http://localhost:3000/blog-api/api/endpoint ``` #### POST Request with JSON Data ```bash node scripts/test-auth-route.js \ http://localhost:3000/blog-api/777/submit \ POST \ '{"responses":{"4577":"13295"},"submissionID":5,"stepInstanceId":"11"}' ``` #### What the Script Does 1. Gets a refresh token from Keycloak - Username: `testuser` - Password: `testpassword` 2. Signs the token with JWT secret from `config.ini` 3. Creates cookie header: `refresh_token=` 4. Makes the authenticated request 5. Shows the exact curl command to reproduce manually #### Script Output The script outputs: - The request details - The response status and body - A curl command for manual reproduction **Note**: The script is verbose - look for the actual response in the output. ### Method 2: Manual curl with Token Use the curl command from the test-auth-route.js output: ```bash # The script outputs something like: # 💡 To test manually with curl: # curl -b "refresh_token=eyJhbGci..." http://localhost:3000/blog-api/api/endpoint # Copy and modify that curl command: curl -X POST http://localhost:3000/blog-api/777/submit \ -H "Content-Type: application/json" \ -b "refresh_token=" \ -d '{"your": "data"}' ``` ### Method 3: Mock Authentication (Development Only - EASIEST) For development, bypass Keycloak entirely using mock auth. #### Setup ```bash # Add to service .env file (e.g., blog-api/.env) MOCK_AUTH=true MOCK_USER_ID=test-user MOCK_USER_ROLES=admin,operations ``` #### Usage ```bash curl -H "X-Mock-Auth: true" \ -H "X-Mock-User: test-user" \ -H "X-Mock-Roles: admin,operations" \ http://localhost:3002/api/protected ``` #### Mock Auth Requirements Mock auth ONLY works when: - `NODE_ENV` is `development` or `test` - The `mockAuth` middleware is added to the route - Will NEVER work in production (security feature) ## Common Testing Patterns ### Test Form Submission ```bash node scripts/test-auth-route.js \ http://localhost:3000/blog-api/777/submit \ POST \ '{"responses":{"4577":"13295"},"submissionID":5,"stepInstanceId":"11"}' ``` ### Test Workflow Start ```bash node scripts/test-auth-route.js \ http://localhost:3002/api/workflow/start \ POST \ '{"workflowCode":"DHS_CLOSEOUT","entityType":"Submission","entityID":123}' ``` ### Test Workflow Step Completion ```bash node scripts/test-auth-route.js \ http://localhost:3002/api/workflow/step/complete \ POST \ '{"stepInstanceID":789,"answers":{"decision":"approved","comments":"Looks good"}}' ``` ### Test GET with Query Parameters ```bash node scripts/test-auth-route.js \ "http://localhost:3002/api/workflows?status=active&limit=10" ``` ### Test File Upload ```bash # Get token from test-auth-route.js first, then: curl -X POST http://localhost:5000/upload \ -H "Content-Type: multipart/form-data" \ -b "refresh_token=" \ -F "file=@/path/to/file.pdf" \ -F "metadata={\"description\":\"Test file\"}" ``` ## Hardcoded Test Credentials The `test-auth-route.js` script uses these credentials: - **Username**: `testuser` - **Password**: `testpassword` - **Keycloak URL**: From `config.ini` (usually `http://localhost:8081`) - **Realm**: `yourRealm` - **Client ID**: From `config.ini` ## Service Ports | Service | Port | Base URL | |---------|------|----------| | Users | 3000 | http://localhost:3000 | | Projects| 3001 | http://localhost:3001 | | Form | 3002 | http://localhost:3002 | | Email | 3003 | http://localhost:3003 | | Uploads | 5000 | http://localhost:5000 | ## Route Prefixes Check `/src/app.ts` in each service for route prefixes: ```typescript // Example from blog-api/src/app.ts app.use('/blog-api/api', formRoutes); // Prefix: /blog-api/api app.use('/api/workflow', workflowRoutes); // Prefix: /api/workflow ``` **Full Route** = Base URL + Prefix + Route Path Example: - Base: `http://localhost:3002` - Prefix: `/form` - Route: `/777/submit` - **Full URL**: `http://localhost:3000/blog-api/777/submit` ## Testing Checklist Before testing a route: - [ ] Identify the service (form, email, users, etc.) - [ ] Find the correct port - [ ] Check route prefixes in `app.ts` - [ ] Construct the full URL - [ ] Prepare request body (if POST/PUT) - [ ] Determine authentication method - [ ] Run the test - [ ] Verify response status and data - [ ] Check database changes if applicable ## Verifying Database Changes After testing routes that modify data: ```bash # Connect to MySQL docker exec -i local-mysql mysql -u root -ppassword1 blog_dev # Check specific table mysql> SELECT * FROM WorkflowInstance WHERE id = 123; mysql> SELECT * FROM WorkflowStepInstance WHERE instanceId = 123; mysql> SELECT * FROM WorkflowNotification WHERE recipientUserId = 'user-123'; ``` ## Debugging Failed Tests ### 401 Unauthorized **Possible causes**: 1. Token expired (regenerate with test-auth-route.js) 2. Incorrect cookie format 3. JWT secret mismatch 4. Keycloak not running **Solutions**: ```bash # Check Keycloak is running docker ps | grep keycloak # Regenerate token node scripts/test-auth-route.js http://localhost:3002/api/health # Verify config.ini has correct jwtSecret ``` ### 403 Forbidden **Possible causes**: 1. User lacks required role 2. Resource permissions incorrect 3. Route requires specific permissions **Solutions**: ```bash # Use mock auth with admin role curl -H "X-Mock-Auth: true" \ -H "X-Mock-User: test-admin" \ -H "X-Mock-Roles: admin" \ http://localhost:3002/api/protected ``` ### 404 Not Found **Possible causes**: 1. Incorrect URL 2. Missing route prefix 3. Route not registered **Solutions**: 1. Check `app.ts` for route prefixes 2. Verify route registration 3. Check service is running (`pm2 list`) ### 500 Internal Server Error **Possible causes**: 1. Database connection issue 2. Missing required fields 3. Validation error 4. Application error **Solutions**: 1. Check service logs (`pm2 logs `) 2. Check Sentry for error details 3. Verify request body matches expected schema 4. Check database connectivity ## Using auth-route-tester Agent For comprehensive route testing after making changes: 1. **Identify affected routes** 2. **Gather route information**: - Full route path (with prefix) - Expected POST data - Tables to verify 3. **Invoke auth-route-tester agent** The agent will: - Test the route with proper authentication - Verify database changes - Check response format - Report any issues ## Example Test Scenarios ### After Creating a New Route ```bash # 1. Test with valid data node scripts/test-auth-route.js \ http://localhost:3002/api/my-new-route \ POST \ '{"field1":"value1","field2":"value2"}' # 2. Verify database docker exec -i local-mysql mysql -u root -ppassword1 blog_dev \ -e "SELECT * FROM MyTable ORDER BY createdAt DESC LIMIT 1;" # 3. Test with invalid data node scripts/test-auth-route.js \ http://localhost:3002/api/my-new-route \ POST \ '{"field1":"invalid"}' # 4. Test without authentication curl http://localhost:3002/api/my-new-route # Should return 401 ``` ### After Modifying a Route ```bash # 1. Test existing functionality still works node scripts/test-auth-route.js \ http://localhost:3002/api/existing-route \ POST \ '{"existing":"data"}' # 2. Test new functionality node scripts/test-auth-route.js \ http://localhost:3002/api/existing-route \ POST \ '{"new":"field","existing":"data"}' # 3. Verify backward compatibility # Test with old request format (if applicable) ``` ## Configuration Files ### config.ini (each service) ```ini [keycloak] url = http://localhost:8081 realm = yourRealm clientId = app-client [jwt] jwtSecret = your-jwt-secret-here ``` ### .env (each service) ```bash NODE_ENV=development MOCK_AUTH=true # Optional: Enable mock auth MOCK_USER_ID=test-user # Optional: Default mock user MOCK_USER_ROLES=admin # Optional: Default mock roles ``` ## Key Files - `/root/git/your project_pre/scripts/test-auth-route.js` - Main testing script - `/blog-api/src/app.ts` - Form service routes - `/notifications/src/app.ts` - Email service routes - `/auth/src/app.ts` - Users service routes - `/config.ini` - Service configuration - `/.env` - Environment variables ## Related Skills - Use **database-verification** to verify database changes - Use **error-tracking** to check for captured errors - Use **workflow-builder** for workflow route testing - Use **notification-sender** to verify notifications sent ================================================ FILE: .claude/skills/skill-developer/ADVANCED.md ================================================ # Advanced Topics & Future Enhancements Ideas and concepts for future improvements to the skill system. --- ## Dynamic Rule Updates **Current State:** Requires Claude Code restart to pick up changes to skill-rules.json **Future Enhancement:** Hot-reload configuration without restart **Implementation Ideas:** - Watch skill-rules.json for changes - Reload on file modification - Invalidate cached compiled regexes - Notify user of reload **Benefits:** - Faster iteration during skill development - No need to restart Claude Code - Better developer experience --- ## Skill Dependencies **Current State:** Skills are independent **Future Enhancement:** Specify skill dependencies and load order **Configuration Idea:** ```json { "my-advanced-skill": { "dependsOn": ["prerequisite-skill", "base-skill"], "type": "domain", ... } } ``` **Use Cases:** - Advanced skill builds on base skill knowledge - Ensure foundational skills loaded first - Chain skills for complex workflows **Benefits:** - Better skill composition - Clearer skill relationships - Progressive disclosure --- ## Conditional Enforcement **Current State:** Enforcement level is static **Future Enhancement:** Enforce based on context or environment **Configuration Idea:** ```json { "enforcement": { "default": "suggest", "when": { "production": "block", "development": "suggest", "ci": "block" } } } ``` **Use Cases:** - Stricter enforcement in production - Relaxed rules during development - CI/CD pipeline requirements **Benefits:** - Environment-appropriate enforcement - Flexible rule application - Context-aware guardrails --- ## Skill Analytics **Current State:** No usage tracking **Future Enhancement:** Track skill usage patterns and effectiveness **Metrics to Collect:** - Skill trigger frequency - False positive rate - False negative rate - Time to skill usage after suggestion - User override rate (skip markers, env vars) - Performance metrics (execution time) **Dashbord Ideas:** - Most/least used skills - Skills with highest false positive rate - Performance bottlenecks - Skill effectiveness scores **Benefits:** - Data-driven skill improvement - Identify problems early - Optimize patterns based on real usage --- ## Skill Versioning **Current State:** No version tracking **Future Enhancement:** Version skills and track compatibility **Configuration Idea:** ```json { "my-skill": { "version": "2.1.0", "minClaudeVersion": "1.5.0", "changelog": "Added support for new workflow patterns", ... } } ``` **Benefits:** - Track skill evolution - Ensure compatibility - Document changes - Support migration paths --- ## Multi-Language Support **Current State:** English only **Future Enhancement:** Support multiple languages for skill content **Implementation Ideas:** - Language-specific SKILL.md variants - Automatic language detection - Fallback to English **Use Cases:** - International teams - Localized documentation - Multi-language projects --- ## Skill Testing Framework **Current State:** Manual testing with npx tsx commands **Future Enhancement:** Automated skill testing **Features:** - Test cases for trigger patterns - Assertion framework - CI/CD integration - Coverage reports **Example Test:** ```typescript describe('database-verification', () => { it('triggers on Prisma imports', () => { const result = testSkill({ prompt: "add user tracking", file: "services/user.ts", content: "import { PrismaService } from './prisma'" }); expect(result.triggered).toBe(true); expect(result.skill).toBe('database-verification'); }); }); ``` **Benefits:** - Prevent regressions - Validate patterns before deployment - Confidence in changes --- ## Related Files - [SKILL.md](SKILL.md) - Main skill guide - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Current debugging guide - [HOOK_MECHANISMS.md](HOOK_MECHANISMS.md) - How hooks work today ================================================ FILE: .claude/skills/skill-developer/HOOK_MECHANISMS.md ================================================ # Hook Mechanisms - Deep Dive Technical deep dive into how the UserPromptSubmit and PreToolUse hooks work. ## Table of Contents - [UserPromptSubmit Hook Flow](#userpromptsubmit-hook-flow) - [PreToolUse Hook Flow](#pretooluse-hook-flow) - [Exit Code Behavior (CRITICAL)](#exit-code-behavior-critical) - [Session State Management](#session-state-management) - [Performance Considerations](#performance-considerations) --- ## UserPromptSubmit Hook Flow ### Execution Sequence ``` User submits prompt ↓ .claude/settings.json registers hook ↓ skill-activation-prompt.sh executes ↓ npx tsx skill-activation-prompt.ts ↓ Hook reads stdin (JSON with prompt) ↓ Loads skill-rules.json ↓ Matches keywords + intent patterns ↓ Groups matches by priority (critical → high → medium → low) ↓ Outputs formatted message to stdout ↓ stdout becomes context for Claude (injected before prompt) ↓ Claude sees: [skill suggestion] + user's prompt ``` ### Key Points - **Exit code**: Always 0 (allow) - **stdout**: → Claude's context (injected as system message) - **Timing**: Runs BEFORE Claude processes prompt - **Behavior**: Non-blocking, advisory only - **Purpose**: Make Claude aware of relevant skills ### Input Format ```json { "session_id": "abc-123", "transcript_path": "/path/to/transcript.json", "cwd": "/root/git/your-project", "permission_mode": "normal", "hook_event_name": "UserPromptSubmit", "prompt": "how does the layout system work?" } ``` ### Output Format (to stdout) ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 🎯 SKILL ACTIVATION CHECK ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 📚 RECOMMENDED SKILLS: → project-catalog-developer ACTION: Use Skill tool BEFORE responding ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` Claude sees this output as additional context before processing the user's prompt. --- ## PreToolUse Hook Flow ### Execution Sequence ``` Claude calls Edit/Write tool ↓ .claude/settings.json registers hook (matcher: Edit|Write) ↓ skill-verification-guard.sh executes ↓ npx tsx skill-verification-guard.ts ↓ Hook reads stdin (JSON with tool_name, tool_input) ↓ Loads skill-rules.json ↓ Checks file path patterns (glob matching) ↓ Reads file for content patterns (if file exists) ↓ Checks session state (was skill already used?) ↓ Checks skip conditions (file markers, env vars) ↓ IF MATCHED AND NOT SKIPPED: Update session state (mark skill as enforced) Output block message to stderr Exit with code 2 (BLOCK) ELSE: Exit with code 0 (ALLOW) ↓ IF BLOCKED: stderr → Claude sees message Edit/Write tool does NOT execute Claude must use skill and retry IF ALLOWED: Tool executes normally ``` ### Key Points - **Exit code 2**: BLOCK (stderr → Claude) - **Exit code 0**: ALLOW - **Timing**: Runs BEFORE tool execution - **Session tracking**: Prevents repeated blocks in same session - **Fail open**: On errors, allows operation (don't break workflow) - **Purpose**: Enforce critical guardrails ### Input Format ```json { "session_id": "abc-123", "transcript_path": "/path/to/transcript.json", "cwd": "/root/git/your-project", "permission_mode": "normal", "hook_event_name": "PreToolUse", "tool_name": "Edit", "tool_input": { "file_path": "/root/git/your-project/form/src/services/user.ts", "old_string": "...", "new_string": "..." } } ``` ### Output Format (to stderr when blocked) ``` ⚠️ BLOCKED - Database Operation Detected 📋 REQUIRED ACTION: 1. Use Skill tool: 'database-verification' 2. Verify ALL table and column names against schema 3. Check database structure with DESCRIBE commands 4. Then retry this edit Reason: Prevent column name errors in Prisma queries File: form/src/services/user.ts 💡 TIP: Add '// @skip-validation' comment to skip future checks ``` Claude receives this message and understands it needs to use the skill before retrying the edit. --- ## Exit Code Behavior (CRITICAL) ### Exit Code Reference Table | Exit Code | stdout | stderr | Tool Execution | Claude Sees | |-----------|--------|--------|----------------|-------------| | 0 (UserPromptSubmit) | → Context | → User only | N/A | stdout content | | 0 (PreToolUse) | → User only | → User only | **Proceeds** | Nothing | | 2 (PreToolUse) | → User only | → **CLAUDE** | **BLOCKED** | stderr content | | Other | → User only | → User only | Blocked | Nothing | ### Why Exit Code 2 Matters This is THE critical mechanism for enforcement: 1. **Only way** to send message to Claude from PreToolUse 2. stderr content is "fed back to Claude automatically" 3. Claude sees the block message and understands what to do 4. Tool execution is prevented 5. Critical for enforcement of guardrails ### Example Conversation Flow ``` User: "Add a new user service with Prisma" Claude: "I'll create the user service..." [Attempts to Edit form/src/services/user.ts] PreToolUse Hook: [Exit code 2] stderr: "⚠️ BLOCKED - Use database-verification" Claude sees error, responds: "I need to verify the database schema first." [Uses Skill tool: database-verification] [Verifies column names] [Retries Edit - now allowed (session tracking)] ``` --- ## Session State Management ### Purpose Prevent repeated nagging in the same session - once Claude uses a skill, don't block again. ### State File Location `.claude/hooks/state/skills-used-{session_id}.json` ### State File Structure ```json { "skills_used": [ "database-verification", "error-tracking" ], "files_verified": [] } ``` ### How It Works 1. **First edit** of file with Prisma: - Hook blocks with exit code 2 - Updates session state: adds "database-verification" to skills_used - Claude sees message, uses skill 2. **Second edit** (same session): - Hook checks session state - Finds "database-verification" in skills_used - Exits with code 0 (allow) - No message to Claude 3. **Different session**: - New session ID = new state file - Hook blocks again ### Limitation The hook cannot detect when the skill is *actually* invoked - it just blocks once per session per skill. This means: - If Claude doesn't use the skill but makes a different edit, it won't block again - Trust that Claude follows the instruction - Future enhancement: detect actual Skill tool usage --- ## Performance Considerations ### Target Metrics - **UserPromptSubmit**: < 100ms - **PreToolUse**: < 200ms ### Performance Bottlenecks 1. **Loading skill-rules.json** (every execution) - Future: Cache in memory - Future: Watch for changes, reload only when needed 2. **Reading file content** (PreToolUse) - Only when contentPatterns configured - Only if file exists - Can be slow for large files 3. **Glob matching** (PreToolUse) - Regex compilation for each pattern - Future: Compile once, cache 4. **Regex matching** (Both hooks) - Intent patterns (UserPromptSubmit) - Content patterns (PreToolUse) - Future: Lazy compile, cache compiled regexes ### Optimization Strategies **Reduce patterns:** - Use more specific patterns (fewer to check) - Combine similar patterns where possible **File path patterns:** - More specific = fewer files to check - Example: `form/src/services/**` better than `form/**` **Content patterns:** - Only add when truly necessary - Simpler regex = faster matching --- **Related Files:** - [SKILL.md](SKILL.md) - Main skill guide - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Debug hook issues - [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) - Configuration reference ================================================ FILE: .claude/skills/skill-developer/PATTERNS_LIBRARY.md ================================================ # Common Patterns Library Ready-to-use regex and glob patterns for skill triggers. Copy and customize for your skills. --- ## Intent Patterns (Regex) ### Feature/Endpoint Creation ```regex (add|create|implement|build).*?(feature|endpoint|route|service|controller) ``` ### Component Creation ```regex (create|add|make|build).*?(component|UI|page|modal|dialog|form) ``` ### Database Work ```regex (add|create|modify|update).*?(user|table|column|field|schema|migration) (database|prisma).*?(change|update|query) ``` ### Error Handling ```regex (fix|handle|catch|debug).*?(error|exception|bug) (add|implement).*?(try|catch|error.*?handling) ``` ### Explanation Requests ```regex (how does|how do|explain|what is|describe|tell me about).*? ``` ### Workflow Operations ```regex (create|add|modify|update).*?(workflow|step|branch|condition) (debug|troubleshoot|fix).*?workflow ``` ### Testing ```regex (write|create|add).*?(test|spec|unit.*?test) ``` --- ## File Path Patterns (Glob) ### Frontend ```glob frontend/src/**/*.tsx # All React components frontend/src/**/*.ts # All TypeScript files frontend/src/components/** # Only components directory ``` ### Backend Services ```glob form/src/**/*.ts # Form service email/src/**/*.ts # Email service users/src/**/*.ts # Users service projects/src/**/*.ts # Projects service ``` ### Database ```glob **/schema.prisma # Prisma schema (anywhere) **/migrations/**/*.sql # Migration files database/src/**/*.ts # Database scripts ``` ### Workflows ```glob form/src/workflow/**/*.ts # Workflow engine form/src/workflow-definitions/**/*.json # Workflow definitions ``` ### Test Exclusions ```glob **/*.test.ts # TypeScript tests **/*.test.tsx # React component tests **/*.spec.ts # Spec files ``` --- ## Content Patterns (Regex) ### Prisma/Database ```regex import.*[Pp]risma # Prisma imports PrismaService # PrismaService usage prisma\. # prisma.something \.findMany\( # Prisma query methods \.create\( \.update\( \.delete\( ``` ### Controllers/Routes ```regex export class.*Controller # Controller classes router\. # Express router app\.(get|post|put|delete|patch) # Express app routes ``` ### Error Handling ```regex try\s*\{ # Try blocks catch\s*\( # Catch blocks throw new # Throw statements ``` ### React/Components ```regex export.*React\.FC # React functional components export default function.* # Default function exports useState|useEffect # React hooks ``` --- **Usage Example:** ```json { "my-skill": { "promptTriggers": { "intentPatterns": [ "(create|add|build).*?(component|UI|page)" ] }, "fileTriggers": { "pathPatterns": [ "frontend/src/**/*.tsx" ], "contentPatterns": [ "export.*React\\.FC", "useState|useEffect" ] } } } ``` --- **Related Files:** - [SKILL.md](SKILL.md) - Main skill guide - [TRIGGER_TYPES.md](TRIGGER_TYPES.md) - Detailed trigger documentation - [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) - Complete schema ================================================ FILE: .claude/skills/skill-developer/SKILL.md ================================================ --- name: skill-developer description: Create and manage Claude Code skills following Anthropic best practices. Use when creating new skills, modifying skill-rules.json, understanding trigger patterns, working with hooks, debugging skill activation, or implementing progressive disclosure. Covers skill structure, YAML frontmatter, trigger types (keywords, intent patterns, file paths, content patterns), enforcement levels (block, suggest, warn), hook mechanisms (UserPromptSubmit, PreToolUse), session tracking, and the 500-line rule. --- # Skill Developer Guide ## Purpose Comprehensive guide for creating and managing skills in Claude Code with auto-activation system, following Anthropic's official best practices including the 500-line rule and progressive disclosure pattern. ## When to Use This Skill Automatically activates when you mention: - Creating or adding skills - Modifying skill triggers or rules - Understanding how skill activation works - Debugging skill activation issues - Working with skill-rules.json - Hook system mechanics - Claude Code best practices - Progressive disclosure - YAML frontmatter - 500-line rule --- ## System Overview ### Two-Hook Architecture **1. UserPromptSubmit Hook** (Proactive Suggestions) - **File**: `.claude/hooks/skill-activation-prompt.ts` - **Trigger**: BEFORE Claude sees user's prompt - **Purpose**: Suggest relevant skills based on keywords + intent patterns - **Method**: Injects formatted reminder as context (stdout → Claude's input) - **Use Cases**: Topic-based skills, implicit work detection **2. Stop Hook - Error Handling Reminder** (Gentle Reminders) - **File**: `.claude/hooks/error-handling-reminder.ts` - **Trigger**: AFTER Claude finishes responding - **Purpose**: Gentle reminder to self-assess error handling in code written - **Method**: Analyzes edited files for risky patterns, displays reminder if needed - **Use Cases**: Error handling awareness without blocking friction **Philosophy Change (2025-10-27):** We moved away from blocking PreToolUse for Sentry/error handling. Instead, use gentle post-response reminders that don't block workflow but maintain code quality awareness. ### Configuration File **Location**: `.claude/skills/skill-rules.json` Defines: - All skills and their trigger conditions - Enforcement levels (block, suggest, warn) - File path patterns (glob) - Content detection patterns (regex) - Skip conditions (session tracking, file markers, env vars) --- ## Skill Types ### 1. Guardrail Skills **Purpose:** Enforce critical best practices that prevent errors **Characteristics:** - Type: `"guardrail"` - Enforcement: `"block"` - Priority: `"critical"` or `"high"` - Block file edits until skill used - Prevent common mistakes (column names, critical errors) - Session-aware (don't repeat nag in same session) **Examples:** - `database-verification` - Verify table/column names before Prisma queries - `frontend-dev-guidelines` - Enforce React/TypeScript patterns **When to Use:** - Mistakes that cause runtime errors - Data integrity concerns - Critical compatibility issues ### 2. Domain Skills **Purpose:** Provide comprehensive guidance for specific areas **Characteristics:** - Type: `"domain"` - Enforcement: `"suggest"` - Priority: `"high"` or `"medium"` - Advisory, not mandatory - Topic or domain-specific - Comprehensive documentation **Examples:** - `backend-dev-guidelines` - Node.js/Express/TypeScript patterns - `frontend-dev-guidelines` - React/TypeScript best practices - `error-tracking` - Sentry integration guidance **When to Use:** - Complex systems requiring deep knowledge - Best practices documentation - Architectural patterns - How-to guides --- ## Quick Start: Creating a New Skill ### Step 1: Create Skill File **Location:** `.claude/skills/{skill-name}/SKILL.md` **Template:** ```markdown --- name: my-new-skill description: Brief description including keywords that trigger this skill. Mention topics, file types, and use cases. Be explicit about trigger terms. --- # My New Skill ## Purpose What this skill helps with ## When to Use Specific scenarios and conditions ## Key Information The actual guidance, documentation, patterns, examples ``` **Best Practices:** - ✅ **Name**: Lowercase, hyphens, gerund form (verb + -ing) preferred - ✅ **Description**: Include ALL trigger keywords/phrases (max 1024 chars) - ✅ **Content**: Under 500 lines - use reference files for details - ✅ **Examples**: Real code examples - ✅ **Structure**: Clear headings, lists, code blocks ### Step 2: Add to skill-rules.json See [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) for complete schema. **Basic Template:** ```json { "my-new-skill": { "type": "domain", "enforcement": "suggest", "priority": "medium", "promptTriggers": { "keywords": ["keyword1", "keyword2"], "intentPatterns": ["(create|add).*?something"] } } } ``` ### Step 3: Test Triggers **Test UserPromptSubmit:** ```bash echo '{"session_id":"test","prompt":"your test prompt"}' | \ npx tsx .claude/hooks/skill-activation-prompt.ts ``` **Test PreToolUse:** ```bash cat <<'EOF' | npx tsx .claude/hooks/skill-verification-guard.ts {"session_id":"test","tool_name":"Edit","tool_input":{"file_path":"test.ts"}} EOF ``` ### Step 4: Refine Patterns Based on testing: - Add missing keywords - Refine intent patterns to reduce false positives - Adjust file path patterns - Test content patterns against actual files ### Step 5: Follow Anthropic Best Practices ✅ Keep SKILL.md under 500 lines ✅ Use progressive disclosure with reference files ✅ Add table of contents to reference files > 100 lines ✅ Write detailed description with trigger keywords ✅ Test with 3+ real scenarios before documenting ✅ Iterate based on actual usage --- ## Enforcement Levels ### BLOCK (Critical Guardrails) - Physically prevents Edit/Write tool execution - Exit code 2 from hook, stderr → Claude - Claude sees message and must use skill to proceed - **Use For**: Critical mistakes, data integrity, security issues **Example:** Database column name verification ### SUGGEST (Recommended) - Reminder injected before Claude sees prompt - Claude is aware of relevant skills - Not enforced, just advisory - **Use For**: Domain guidance, best practices, how-to guides **Example:** Frontend development guidelines ### WARN (Optional) - Low priority suggestions - Advisory only, minimal enforcement - **Use For**: Nice-to-have suggestions, informational reminders **Rarely used** - most skills are either BLOCK or SUGGEST. --- ## Skip Conditions & User Control ### 1. Session Tracking **Purpose:** Don't nag repeatedly in same session **How it works:** - First edit → Hook blocks, updates session state - Second edit (same session) → Hook allows - Different session → Blocks again **State File:** `.claude/hooks/state/skills-used-{session_id}.json` ### 2. File Markers **Purpose:** Permanent skip for verified files **Marker:** `// @skip-validation` **Usage:** ```typescript // @skip-validation import { PrismaService } from './prisma'; // This file has been manually verified ``` **NOTE:** Use sparingly - defeats the purpose if overused ### 3. Environment Variables **Purpose:** Emergency disable, temporary override **Global disable:** ```bash export SKIP_SKILL_GUARDRAILS=true # Disables ALL PreToolUse blocks ``` **Skill-specific:** ```bash export SKIP_DB_VERIFICATION=true export SKIP_ERROR_REMINDER=true ``` --- ## Testing Checklist When creating a new skill, verify: - [ ] Skill file created in `.claude/skills/{name}/SKILL.md` - [ ] Proper frontmatter with name and description - [ ] Entry added to `skill-rules.json` - [ ] Keywords tested with real prompts - [ ] Intent patterns tested with variations - [ ] File path patterns tested with actual files - [ ] Content patterns tested against file contents - [ ] Block message is clear and actionable (if guardrail) - [ ] Skip conditions configured appropriately - [ ] Priority level matches importance - [ ] No false positives in testing - [ ] No false negatives in testing - [ ] Performance is acceptable (<100ms or <200ms) - [ ] JSON syntax validated: `jq . skill-rules.json` - [ ] **SKILL.md under 500 lines** ⭐ - [ ] Reference files created if needed - [ ] Table of contents added to files > 100 lines --- ## Reference Files For detailed information on specific topics, see: ### [TRIGGER_TYPES.md](TRIGGER_TYPES.md) Complete guide to all trigger types: - Keyword triggers (explicit topic matching) - Intent patterns (implicit action detection) - File path triggers (glob patterns) - Content patterns (regex in files) - Best practices and examples for each - Common pitfalls and testing strategies ### [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) Complete skill-rules.json schema: - Full TypeScript interface definitions - Field-by-field explanations - Complete guardrail skill example - Complete domain skill example - Validation guide and common errors ### [HOOK_MECHANISMS.md](HOOK_MECHANISMS.md) Deep dive into hook internals: - UserPromptSubmit flow (detailed) - PreToolUse flow (detailed) - Exit code behavior table (CRITICAL) - Session state management - Performance considerations ### [TROUBLESHOOTING.md](TROUBLESHOOTING.md) Comprehensive debugging guide: - Skill not triggering (UserPromptSubmit) - PreToolUse not blocking - False positives (too many triggers) - Hook not executing at all - Performance issues ### [PATTERNS_LIBRARY.md](PATTERNS_LIBRARY.md) Ready-to-use pattern collection: - Intent pattern library (regex) - File path pattern library (glob) - Content pattern library (regex) - Organized by use case - Copy-paste ready ### [ADVANCED.md](ADVANCED.md) Future enhancements and ideas: - Dynamic rule updates - Skill dependencies - Conditional enforcement - Skill analytics - Skill versioning --- ## Quick Reference Summary ### Create New Skill (5 Steps) 1. Create `.claude/skills/{name}/SKILL.md` with frontmatter 2. Add entry to `.claude/skills/skill-rules.json` 3. Test with `npx tsx` commands 4. Refine patterns based on testing 5. Keep SKILL.md under 500 lines ### Trigger Types - **Keywords**: Explicit topic mentions - **Intent**: Implicit action detection - **File Paths**: Location-based activation - **Content**: Technology-specific detection See [TRIGGER_TYPES.md](TRIGGER_TYPES.md) for complete details. ### Enforcement - **BLOCK**: Exit code 2, critical only - **SUGGEST**: Inject context, most common - **WARN**: Advisory, rarely used ### Skip Conditions - **Session tracking**: Automatic (prevents repeated nags) - **File markers**: `// @skip-validation` (permanent skip) - **Env vars**: `SKIP_SKILL_GUARDRAILS` (emergency disable) ### Anthropic Best Practices ✅ **500-line rule**: Keep SKILL.md under 500 lines ✅ **Progressive disclosure**: Use reference files for details ✅ **Table of contents**: Add to reference files > 100 lines ✅ **One level deep**: Don't nest references deeply ✅ **Rich descriptions**: Include all trigger keywords (max 1024 chars) ✅ **Test first**: Build 3+ evaluations before extensive documentation ✅ **Gerund naming**: Prefer verb + -ing (e.g., "processing-pdfs") ### Troubleshoot Test hooks manually: ```bash # UserPromptSubmit echo '{"prompt":"test"}' | npx tsx .claude/hooks/skill-activation-prompt.ts # PreToolUse cat <<'EOF' | npx tsx .claude/hooks/skill-verification-guard.ts {"tool_name":"Edit","tool_input":{"file_path":"test.ts"}} EOF ``` See [TROUBLESHOOTING.md](TROUBLESHOOTING.md) for complete debugging guide. --- ## Related Files **Configuration:** - `.claude/skills/skill-rules.json` - Master configuration - `.claude/hooks/state/` - Session tracking - `.claude/settings.json` - Hook registration **Hooks:** - `.claude/hooks/skill-activation-prompt.ts` - UserPromptSubmit - `.claude/hooks/error-handling-reminder.ts` - Stop event (gentle reminders) **All Skills:** - `.claude/skills/*/SKILL.md` - Skill content files --- **Skill Status**: COMPLETE - Restructured following Anthropic best practices ✅ **Line Count**: < 500 (following 500-line rule) ✅ **Progressive Disclosure**: Reference files for detailed information ✅ **Next**: Create more skills, refine patterns based on usage ================================================ FILE: .claude/skills/skill-developer/SKILL_RULES_REFERENCE.md ================================================ # skill-rules.json - Complete Reference Complete schema and configuration reference for `.claude/skills/skill-rules.json`. ## Table of Contents - [File Location](#file-location) - [Complete TypeScript Schema](#complete-typescript-schema) - [Field Guide](#field-guide) - [Example: Guardrail Skill](#example-guardrail-skill) - [Example: Domain Skill](#example-domain-skill) - [Validation](#validation) --- ## File Location **Path:** `.claude/skills/skill-rules.json` This JSON file defines all skills and their trigger conditions for the auto-activation system. --- ## Complete TypeScript Schema ```typescript interface SkillRules { version: string; skills: Record; } interface SkillRule { type: 'guardrail' | 'domain'; enforcement: 'block' | 'suggest' | 'warn'; priority: 'critical' | 'high' | 'medium' | 'low'; promptTriggers?: { keywords?: string[]; intentPatterns?: string[]; // Regex strings }; fileTriggers?: { pathPatterns: string[]; // Glob patterns pathExclusions?: string[]; // Glob patterns contentPatterns?: string[]; // Regex strings createOnly?: boolean; // Only trigger on file creation }; blockMessage?: string; // For guardrails, {file_path} placeholder skipConditions?: { sessionSkillUsed?: boolean; // Skip if used in session fileMarkers?: string[]; // e.g., ["@skip-validation"] envOverride?: string; // e.g., "SKIP_DB_VERIFICATION" }; } ``` --- ## Field Guide ### Top Level | Field | Type | Required | Description | |-------|------|----------|-------------| | `version` | string | Yes | Schema version (currently "1.0") | | `skills` | object | Yes | Map of skill name → SkillRule | ### SkillRule Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | Yes | "guardrail" (enforced) or "domain" (advisory) | | `enforcement` | string | Yes | "block" (PreToolUse), "suggest" (UserPromptSubmit), or "warn" | | `priority` | string | Yes | "critical", "high", "medium", or "low" | | `promptTriggers` | object | Optional | Triggers for UserPromptSubmit hook | | `fileTriggers` | object | Optional | Triggers for PreToolUse hook | | `blockMessage` | string | Optional* | Required if enforcement="block". Use `{file_path}` placeholder | | `skipConditions` | object | Optional | Escape hatches and session tracking | *Required for guardrails ### promptTriggers Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `keywords` | string[] | Optional | Exact substring matches (case-insensitive) | | `intentPatterns` | string[] | Optional | Regex patterns for intent detection | ### fileTriggers Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `pathPatterns` | string[] | Yes* | Glob patterns for file paths | | `pathExclusions` | string[] | Optional | Glob patterns to exclude (e.g., test files) | | `contentPatterns` | string[] | Optional | Regex patterns to match file content | | `createOnly` | boolean | Optional | Only trigger when creating new files | *Required if fileTriggers is present ### skipConditions Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `sessionSkillUsed` | boolean | Optional | Skip if skill already used this session | | `fileMarkers` | string[] | Optional | Skip if file contains comment marker | | `envOverride` | string | Optional | Environment variable name to disable skill | --- ## Example: Guardrail Skill Complete example of a blocking guardrail skill with all features: ```json { "database-verification": { "type": "guardrail", "enforcement": "block", "priority": "critical", "promptTriggers": { "keywords": [ "prisma", "database", "table", "column", "schema", "query", "migration" ], "intentPatterns": [ "(add|create|implement).*?(user|login|auth|tracking|feature)", "(modify|update|change).*?(table|column|schema|field)", "database.*?(change|update|modify|migration)" ] }, "fileTriggers": { "pathPatterns": [ "**/schema.prisma", "**/migrations/**/*.sql", "database/src/**/*.ts", "form/src/**/*.ts", "email/src/**/*.ts", "users/src/**/*.ts", "projects/src/**/*.ts", "utilities/src/**/*.ts" ], "pathExclusions": [ "**/*.test.ts", "**/*.spec.ts" ], "contentPatterns": [ "import.*[Pp]risma", "PrismaService", "prisma\\.", "\\.findMany\\(", "\\.findUnique\\(", "\\.findFirst\\(", "\\.create\\(", "\\.createMany\\(", "\\.update\\(", "\\.updateMany\\(", "\\.upsert\\(", "\\.delete\\(", "\\.deleteMany\\(" ] }, "blockMessage": "⚠️ BLOCKED - Database Operation Detected\n\n📋 REQUIRED ACTION:\n1. Use Skill tool: 'database-verification'\n2. Verify ALL table and column names against schema\n3. Check database structure with DESCRIBE commands\n4. Then retry this edit\n\nReason: Prevent column name errors in Prisma queries\nFile: {file_path}\n\n💡 TIP: Add '// @skip-validation' comment to skip future checks", "skipConditions": { "sessionSkillUsed": true, "fileMarkers": [ "@skip-validation" ], "envOverride": "SKIP_DB_VERIFICATION" } } } ``` ### Key Points for Guardrails 1. **type**: Must be "guardrail" 2. **enforcement**: Must be "block" 3. **priority**: Usually "critical" or "high" 4. **blockMessage**: Required, clear actionable steps 5. **skipConditions**: Session tracking prevents repeated nagging 6. **fileTriggers**: Usually has both path and content patterns 7. **contentPatterns**: Catch actual usage of technology --- ## Example: Domain Skill Complete example of a suggestion-based domain skill: ```json { "project-catalog-developer": { "type": "domain", "enforcement": "suggest", "priority": "high", "promptTriggers": { "keywords": [ "layout", "layout system", "grid", "grid layout", "toolbar", "column", "cell editor", "cell renderer", "submission", "submissions", "blog dashboard", "datagrid", "data grid", "CustomToolbar", "GridLayoutDialog", "useGridLayout", "auto-save", "column order", "column width", "filter", "sort" ], "intentPatterns": [ "(how does|how do|explain|what is|describe).*?(layout|grid|toolbar|column|submission|catalog)", "(add|create|modify|change).*?(toolbar|column|cell|editor|renderer)", "blog dashboard.*?" ] }, "fileTriggers": { "pathPatterns": [ "frontend/src/features/submissions/**/*.tsx", "frontend/src/features/submissions/**/*.ts" ], "pathExclusions": [ "**/*.test.tsx", "**/*.test.ts" ] } } } ``` ### Key Points for Domain Skills 1. **type**: Must be "domain" 2. **enforcement**: Usually "suggest" 3. **priority**: "high" or "medium" 4. **blockMessage**: Not needed (doesn't block) 5. **skipConditions**: Optional (less critical) 6. **promptTriggers**: Usually has extensive keywords 7. **fileTriggers**: May have only path patterns (content less important) --- ## Validation ### Check JSON Syntax ```bash cat .claude/skills/skill-rules.json | jq . ``` If valid, jq will pretty-print the JSON. If invalid, it will show the error. ### Common JSON Errors **Trailing comma:** ```json { "keywords": ["one", "two",] // ❌ Trailing comma } ``` **Missing quotes:** ```json { type: "guardrail" // ❌ Missing quotes on key } ``` **Single quotes (invalid JSON):** ```json { 'type': 'guardrail' // ❌ Must use double quotes } ``` ### Validation Checklist - [ ] JSON syntax valid (use `jq`) - [ ] All skill names match SKILL.md filenames - [ ] Guardrails have `blockMessage` - [ ] Block messages use `{file_path}` placeholder - [ ] Intent patterns are valid regex (test on regex101.com) - [ ] File path patterns use correct glob syntax - [ ] Content patterns escape special characters - [ ] Priority matches enforcement level - [ ] No duplicate skill names --- **Related Files:** - [SKILL.md](SKILL.md) - Main skill guide - [TRIGGER_TYPES.md](TRIGGER_TYPES.md) - Complete trigger documentation - [TROUBLESHOOTING.md](TROUBLESHOOTING.md) - Debugging configuration issues ================================================ FILE: .claude/skills/skill-developer/TRIGGER_TYPES.md ================================================ # Trigger Types - Complete Guide Complete reference for configuring skill triggers in Claude Code's skill auto-activation system. ## Table of Contents - [Keyword Triggers (Explicit)](#keyword-triggers-explicit) - [Intent Pattern Triggers (Implicit)](#intent-pattern-triggers-implicit) - [File Path Triggers](#file-path-triggers) - [Content Pattern Triggers](#content-pattern-triggers) - [Best Practices Summary](#best-practices-summary) --- ## Keyword Triggers (Explicit) ### How It Works Case-insensitive substring matching in user's prompt. ### Use For Topic-based activation where user explicitly mentions the subject. ### Configuration ```json "promptTriggers": { "keywords": ["layout", "grid", "toolbar", "submission"] } ``` ### Example - User prompt: "how does the **layout** system work?" - Matches: "layout" keyword - Activates: `project-catalog-developer` ### Best Practices - Use specific, unambiguous terms - Include common variations ("layout", "layout system", "grid layout") - Avoid overly generic words ("system", "work", "create") - Test with real prompts --- ## Intent Pattern Triggers (Implicit) ### How It Works Regex pattern matching to detect user's intent even when they don't mention the topic explicitly. ### Use For Action-based activation where user describes what they want to do rather than the specific topic. ### Configuration ```json "promptTriggers": { "intentPatterns": [ "(create|add|implement).*?(feature|endpoint)", "(how does|explain).*?(layout|workflow)" ] } ``` ### Examples **Database Work:** - User prompt: "add user tracking feature" - Matches: `(add).*?(feature)` - Activates: `database-verification`, `error-tracking` **Component Creation:** - User prompt: "create a dashboard widget" - Matches: `(create).*?(component)` (if component in pattern) - Activates: `frontend-dev-guidelines` ### Best Practices - Capture common action verbs: `(create|add|modify|build|implement)` - Include domain-specific nouns: `(feature|endpoint|component|workflow)` - Use non-greedy matching: `.*?` instead of `.*` - Test patterns thoroughly with regex tester (https://regex101.com/) - Don't make patterns too broad (causes false positives) - Don't make patterns too specific (causes false negatives) ### Common Pattern Examples ```regex # Database Work (add|create|implement).*?(user|login|auth|feature) # Explanations (how does|explain|what is|describe).*? # Frontend Work (create|add|make|build).*?(component|UI|page|modal|dialog) # Error Handling (fix|handle|catch|debug).*?(error|exception|bug) # Workflow Operations (create|add|modify).*?(workflow|step|branch|condition) ``` --- ## File Path Triggers ### How It Works Glob pattern matching against the file path being edited. ### Use For Domain/area-specific activation based on file location in the project. ### Configuration ```json "fileTriggers": { "pathPatterns": [ "frontend/src/**/*.tsx", "form/src/**/*.ts" ], "pathExclusions": [ "**/*.test.ts", "**/*.spec.ts" ] } ``` ### Glob Pattern Syntax - `**` = Any number of directories (including zero) - `*` = Any characters within a directory name - Examples: - `frontend/src/**/*.tsx` = All .tsx files in frontend/src and subdirs - `**/schema.prisma` = schema.prisma anywhere in project - `form/src/**/*.ts` = All .ts files in form/src subdirs ### Example - File being edited: `frontend/src/components/Dashboard.tsx` - Matches: `frontend/src/**/*.tsx` - Activates: `frontend-dev-guidelines` ### Best Practices - Be specific to avoid false positives - Use exclusions for test files: `**/*.test.ts` - Consider subdirectory structure - Test patterns with actual file paths - Use narrower patterns when possible: `form/src/services/**` not `form/**` ### Common Path Patterns ```glob # Frontend frontend/src/**/*.tsx # All React components frontend/src/**/*.ts # All TypeScript files frontend/src/components/** # Only components directory # Backend Services form/src/**/*.ts # Form service email/src/**/*.ts # Email service users/src/**/*.ts # Users service # Database **/schema.prisma # Prisma schema (anywhere) **/migrations/**/*.sql # Migration files database/src/**/*.ts # Database scripts # Workflows form/src/workflow/**/*.ts # Workflow engine form/src/workflow-definitions/**/*.json # Workflow definitions # Test Exclusions **/*.test.ts # TypeScript tests **/*.test.tsx # React component tests **/*.spec.ts # Spec files ``` --- ## Content Pattern Triggers ### How It Works Regex pattern matching against the file's actual content (what's inside the file). ### Use For Technology-specific activation based on what the code imports or uses (Prisma, controllers, specific libraries). ### Configuration ```json "fileTriggers": { "contentPatterns": [ "import.*[Pp]risma", "PrismaService", "\\.findMany\\(", "\\.create\\(" ] } ``` ### Examples **Prisma Detection:** - File contains: `import { PrismaService } from '@project/database'` - Matches: `import.*[Pp]risma` - Activates: `database-verification` **Controller Detection:** - File contains: `export class UserController {` - Matches: `export class.*Controller` - Activates: `error-tracking` ### Best Practices - Match imports: `import.*[Pp]risma` (case-insensitive with [Pp]) - Escape special regex chars: `\\.findMany\\(` not `.findMany(` - Patterns use case-insensitive flag - Test against real file content - Make patterns specific enough to avoid false matches ### Common Content Patterns ```regex # Prisma/Database import.*[Pp]risma # Prisma imports PrismaService # PrismaService usage prisma\. # prisma.something \.findMany\( # Prisma query methods \.create\( \.update\( \.delete\( # Controllers/Routes export class.*Controller # Controller classes router\. # Express router app\.(get|post|put|delete|patch) # Express app routes # Error Handling try\s*\{ # Try blocks catch\s*\( # Catch blocks throw new # Throw statements # React/Components export.*React\.FC # React functional components export default function.* # Default function exports useState|useEffect # React hooks ``` --- ## Best Practices Summary ### DO: ✅ Use specific, unambiguous keywords ✅ Test all patterns with real examples ✅ Include common variations ✅ Use non-greedy regex: `.*?` ✅ Escape special characters in content patterns ✅ Add exclusions for test files ✅ Make file path patterns narrow and specific ### DON'T: ❌ Use overly generic keywords ("system", "work") ❌ Make intent patterns too broad (false positives) ❌ Make patterns too specific (false negatives) ❌ Forget to test with regex tester (https://regex101.com/) ❌ Use greedy regex: `.*` instead of `.*?` ❌ Match too broadly in file paths ### Testing Your Triggers **Test keyword/intent triggers:** ```bash echo '{"session_id":"test","prompt":"your test prompt"}' | \ npx tsx .claude/hooks/skill-activation-prompt.ts ``` **Test file path/content triggers:** ```bash cat <<'EOF' | npx tsx .claude/hooks/skill-verification-guard.ts { "session_id": "test", "tool_name": "Edit", "tool_input": {"file_path": "/path/to/test/file.ts"} } EOF ``` --- **Related Files:** - [SKILL.md](SKILL.md) - Main skill guide - [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) - Complete skill-rules.json schema - [PATTERNS_LIBRARY.md](PATTERNS_LIBRARY.md) - Ready-to-use pattern library ================================================ FILE: .claude/skills/skill-developer/TROUBLESHOOTING.md ================================================ # Troubleshooting - Skill Activation Issues Complete debugging guide for skill activation problems. ## Table of Contents - [Skill Not Triggering](#skill-not-triggering) - [UserPromptSubmit Not Suggesting](#userpromptsubmit-not-suggesting) - [PreToolUse Not Blocking](#pretooluse-not-blocking) - [False Positives](#false-positives) - [Hook Not Executing](#hook-not-executing) - [Performance Issues](#performance-issues) --- ## Skill Not Triggering ### UserPromptSubmit Not Suggesting **Symptoms:** Ask a question, but no skill suggestion appears in output. **Common Causes:** #### 1. Keywords Don't Match **Check:** - Look at `promptTriggers.keywords` in skill-rules.json - Are the keywords actually in your prompt? - Remember: case-insensitive substring matching **Example:** ```json "keywords": ["layout", "grid"] ``` - "how does the layout work?" → ✅ Matches "layout" - "how does the grid system work?" → ✅ Matches "grid" - "how do layouts work?" → ✅ Matches "layout" - "how does it work?" → ❌ No match **Fix:** Add more keyword variations to skill-rules.json #### 2. Intent Patterns Too Specific **Check:** - Look at `promptTriggers.intentPatterns` - Test regex at https://regex101.com/ - May need broader patterns **Example:** ```json "intentPatterns": [ "(create|add).*?(database.*?table)" // Too specific ] ``` - "create a database table" → ✅ Matches - "add new table" → ❌ Doesn't match (missing "database") **Fix:** Broaden the pattern: ```json "intentPatterns": [ "(create|add).*?(table|database)" // Better ] ``` #### 3. Typo in Skill Name **Check:** - Skill name in SKILL.md frontmatter - Skill name in skill-rules.json - Must match exactly **Example:** ```yaml # SKILL.md name: project-catalog-developer ``` ```json // skill-rules.json "project-catalogue-developer": { // ❌ Typo: catalogue vs catalog ... } ``` **Fix:** Make names match exactly #### 4. JSON Syntax Error **Check:** ```bash cat .claude/skills/skill-rules.json | jq . ``` If invalid JSON, jq will show the error. **Common errors:** - Trailing commas - Missing quotes - Single quotes instead of double - Unescaped characters in strings **Fix:** Correct JSON syntax, validate with jq #### Debug Command Test the hook manually: ```bash echo '{"session_id":"debug","prompt":"your test prompt here"}' | \ npx tsx .claude/hooks/skill-activation-prompt.ts ``` Expected: Your skill should appear in the output. --- ### PreToolUse Not Blocking **Symptoms:** Edit a file that should trigger a guardrail, but no block occurs. **Common Causes:** #### 1. File Path Doesn't Match Patterns **Check:** - File path being edited - `fileTriggers.pathPatterns` in skill-rules.json - Glob pattern syntax **Example:** ```json "pathPatterns": [ "frontend/src/**/*.tsx" ] ``` - Editing: `frontend/src/components/Dashboard.tsx` → ✅ Matches - Editing: `frontend/tests/Dashboard.test.tsx` → ✅ Matches (add exclusion!) - Editing: `backend/src/app.ts` → ❌ Doesn't match **Fix:** Adjust glob patterns or add the missing path #### 2. Excluded by pathExclusions **Check:** - Are you editing a test file? - Look at `fileTriggers.pathExclusions` **Example:** ```json "pathExclusions": [ "**/*.test.ts", "**/*.spec.ts" ] ``` - Editing: `services/user.test.ts` → ❌ Excluded - Editing: `services/user.ts` → ✅ Not excluded **Fix:** If test exclusion too broad, narrow it or remove #### 3. Content Pattern Not Found **Check:** - Does the file actually contain the pattern? - Look at `fileTriggers.contentPatterns` - Is the regex correct? **Example:** ```json "contentPatterns": [ "import.*[Pp]risma" ] ``` - File has: `import { PrismaService } from './prisma'` → ✅ Matches - File has: `import { Database } from './db'` → ❌ Doesn't match **Debug:** ```bash # Check if pattern exists in file grep -i "prisma" path/to/file.ts ``` **Fix:** Adjust content patterns or add missing imports #### 4. Session Already Used Skill **Check session state:** ```bash ls .claude/hooks/state/ cat .claude/hooks/state/skills-used-{session-id}.json ``` **Example:** ```json { "skills_used": ["database-verification"], "files_verified": [] } ``` If the skill is in `skills_used`, it won't block again in this session. **Fix:** Delete the state file to reset: ```bash rm .claude/hooks/state/skills-used-{session-id}.json ``` #### 5. File Marker Present **Check file for skip marker:** ```bash grep "@skip-validation" path/to/file.ts ``` If found, the file is permanently skipped. **Fix:** Remove the marker if verification is needed again #### 6. Environment Variable Override **Check:** ```bash echo $SKIP_DB_VERIFICATION echo $SKIP_SKILL_GUARDRAILS ``` If set, the skill is disabled. **Fix:** Unset the environment variable: ```bash unset SKIP_DB_VERIFICATION ``` #### Debug Command Test the hook manually: ```bash cat <<'EOF' | npx tsx .claude/hooks/skill-verification-guard.ts 2>&1 { "session_id": "debug", "tool_name": "Edit", "tool_input": {"file_path": "/root/git/your-project/form/src/services/user.ts"} } EOF echo "Exit code: $?" ``` Expected: - Exit code 2 + stderr message if should block - Exit code 0 + no output if should allow --- ## False Positives **Symptoms:** Skill triggers when it shouldn't. **Common Causes & Solutions:** ### 1. Keywords Too Generic **Problem:** ```json "keywords": ["user", "system", "create"] // Too broad ``` - Triggers on: "user manual", "file system", "create directory" **Solution:** Make keywords more specific ```json "keywords": [ "user authentication", "user tracking", "create feature" ] ``` ### 2. Intent Patterns Too Broad **Problem:** ```json "intentPatterns": [ "(create)" // Matches everything with "create" ] ``` - Triggers on: "create file", "create folder", "create account" **Solution:** Add context to patterns ```json "intentPatterns": [ "(create|add).*?(database|table|feature)" // More specific ] ``` **Advanced:** Use negative lookaheads to exclude ```regex (create)(?!.*test).*?(feature) // Don't match if "test" appears ``` ### 3. File Paths Too Generic **Problem:** ```json "pathPatterns": [ "form/**" // Matches everything in form/ ] ``` - Triggers on: test files, config files, everything **Solution:** Use narrower patterns ```json "pathPatterns": [ "form/src/services/**/*.ts", // Only service files "form/src/controllers/**/*.ts" ] ``` ### 4. Content Patterns Catching Unrelated Code **Problem:** ```json "contentPatterns": [ "Prisma" // Matches in comments, strings, etc. ] ``` - Triggers on: `// Don't use Prisma here` - Triggers on: `const note = "Prisma is cool"` **Solution:** Make patterns more specific ```json "contentPatterns": [ "import.*[Pp]risma", // Only imports "PrismaService\\.", // Only actual usage "prisma\\.(findMany|create)" // Specific methods ] ``` ### 5. Adjust Enforcement Level **Last resort:** If false positives are frequent: ```json { "enforcement": "block" // Change to "suggest" } ``` This makes it advisory instead of blocking. --- ## Hook Not Executing **Symptoms:** Hook doesn't run at all - no suggestion, no block. **Common Causes:** ### 1. Hook Not Registered **Check `.claude/settings.json`:** ```bash cat .claude/settings.json | jq '.hooks.UserPromptSubmit' cat .claude/settings.json | jq '.hooks.PreToolUse' ``` Expected: Hook entries present **Fix:** Add missing hook registration: ```json { "hooks": { "UserPromptSubmit": [ { "hooks": [ { "type": "command", "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/skill-activation-prompt.sh" } ] } ] } } ``` ### 2. Bash Wrapper Not Executable **Check:** ```bash ls -l .claude/hooks/*.sh ``` Expected: `-rwxr-xr-x` (executable) **Fix:** ```bash chmod +x .claude/hooks/*.sh ``` ### 3. Incorrect Shebang **Check:** ```bash head -1 .claude/hooks/skill-activation-prompt.sh ``` Expected: `#!/bin/bash` **Fix:** Add correct shebang to first line ### 4. npx/tsx Not Available **Check:** ```bash npx tsx --version ``` Expected: Version number **Fix:** Install dependencies: ```bash cd .claude/hooks npm install ``` ### 5. TypeScript Compilation Error **Check:** ```bash cd .claude/hooks npx tsc --noEmit skill-activation-prompt.ts ``` Expected: No output (no errors) **Fix:** Correct TypeScript syntax errors --- ## Performance Issues **Symptoms:** Hooks are slow, noticeable delay before prompt/edit. **Common Causes:** ### 1. Too Many Patterns **Check:** - Count patterns in skill-rules.json - Each pattern = regex compilation + matching **Solution:** Reduce patterns - Combine similar patterns - Remove redundant patterns - Use more specific patterns (faster matching) ### 2. Complex Regex **Problem:** ```regex (create|add|modify|update|implement|build).*?(feature|endpoint|route|service|controller|component|UI|page) ``` - Long alternations = slow **Solution:** Simplify ```regex (create|add).*?(feature|endpoint) // Fewer alternatives ``` ### 3. Too Many Files Checked **Problem:** ```json "pathPatterns": [ "**/*.ts" // Checks ALL TypeScript files ] ``` **Solution:** Be more specific ```json "pathPatterns": [ "form/src/services/**/*.ts", // Only specific directory "form/src/controllers/**/*.ts" ] ``` ### 4. Large Files Content pattern matching reads entire file - slow for large files. **Solution:** - Only use content patterns when necessary - Consider file size limits (future enhancement) ### Measure Performance ```bash # UserPromptSubmit time echo '{"prompt":"test"}' | npx tsx .claude/hooks/skill-activation-prompt.ts # PreToolUse time cat <<'EOF' | npx tsx .claude/hooks/skill-verification-guard.ts {"tool_name":"Edit","tool_input":{"file_path":"test.ts"}} EOF ``` **Target metrics:** - UserPromptSubmit: < 100ms - PreToolUse: < 200ms --- **Related Files:** - [SKILL.md](SKILL.md) - Main skill guide - [HOOK_MECHANISMS.md](HOOK_MECHANISMS.md) - How hooks work - [SKILL_RULES_REFERENCE.md](SKILL_RULES_REFERENCE.md) - Configuration reference ================================================ FILE: .claude/skills/skill-rules.json ================================================ { "version": "1.0", "description": "Skill activation triggers for Claude Code. Controls when skills automatically suggest or block actions.", "skills": { "skill-developer": { "type": "domain", "enforcement": "suggest", "priority": "high", "description": "Meta-skill for creating and managing Claude Code skills", "promptTriggers": { "keywords": [ "skill system", "create skill", "add skill", "skill triggers", "skill rules", "hook system", "skill development", "skill-rules.json" ], "intentPatterns": [ "(how do|how does|explain).*?skill", "(create|add|modify|build).*?skill", "skill.*?(work|trigger|activate|system)" ] } }, "backend-dev-guidelines": { "type": "domain", "enforcement": "suggest", "priority": "high", "description": "Backend development patterns for Node.js/Express/TypeScript", "promptTriggers": { "keywords": [ "backend", "backend development", "microservice", "controller", "service", "repository", "route", "routing", "express", "API", "endpoint", "middleware", "validation", "Zod", "Prisma", "database access", "BaseController", "dependency injection", "unifiedConfig", "configuration" ], "intentPatterns": [ "(create|add|implement|build).*?(route|endpoint|API|controller|service|repository)", "(fix|handle|debug).*?(error|exception|backend)", "(add|implement).*?(middleware|validation|error.*?handling)", "(organize|structure|refactor).*?(backend|service|API)", "(how to|best practice).*?(backend|route|controller|service)" ] }, "fileTriggers": { "pathPatterns": [ "blog-api/src/**/*.ts", "auth-service/src/**/*.ts", "notifications-service/src/**/*.ts", "backend/**/*.ts", "api/**/*.ts", "server/**/*.ts", "services/**/*.ts" ], "pathExclusions": [ "**/*.test.ts", "**/*.spec.ts" ], "contentPatterns": [ "router\\.", "app\\.(get|post|put|delete|patch)", "export.*Controller", "export.*Service", "prisma\\." ] } }, "frontend-dev-guidelines": { "type": "guardrail", "enforcement": "block", "priority": "high", "description": "React/TypeScript best practices including MUI v7 compatibility", "promptTriggers": { "keywords": [ "component", "react component", "UI", "interface", "page", "modal", "dialog", "form", "MUI", "Material-UI", "Grid", "styling", "frontend", "React" ], "intentPatterns": [ "(create|add|make|build|update|modify|edit).*?(component|UI|page|modal|dialog|form)", "(how to|best practice).*?(component|react|MUI)", "(style|design|layout).*?(component|UI)" ] }, "fileTriggers": { "pathPatterns": [ "frontend/src/**/*.tsx", "frontend/src/**/*.ts", "client/src/**/*.tsx", "client/src/**/*.ts", "src/**/*.tsx" ], "pathExclusions": [ "**/*.test.tsx", "**/*.test.ts", "**/*.spec.tsx", "**/*.spec.ts", "**/*.styles.ts", "**/vite.config.ts", "**/tsconfig.json" ], "contentPatterns": [ "from '@mui/material';", "import.*Grid.*from.*@mui", " **This is NOT a working application** - it's a reference library. Copy what you need into your own projects. --- ## What's Inside **Production-tested infrastructure for:** - ✅ **Auto-activating skills** via hooks - ✅ **Modular skill pattern** (500-line rule with progressive disclosure) - ✅ **Specialized agents** for complex tasks - ✅ **Dev docs system** that survives context resets - ✅ **Comprehensive examples** using generic blog domain **Time investment to build:** 6 months of iteration **Time to integrate into your project:** 15-30 minutes --- ## Quick Start - Pick Your Path ### 🤖 Using Claude Code to Integrate? **Claude:** Read [`CLAUDE_INTEGRATION_GUIDE.md`](CLAUDE_INTEGRATION_GUIDE.md) for step-by-step integration instructions tailored for AI-assisted setup. ### 🎯 I want skill auto-activation **The breakthrough feature:** Skills that actually activate when you need them. **What you need:** 1. The skill-activation hooks (2 files) 2. A skill or two relevant to your work 3. 15 minutes **👉 [Setup Guide: .claude/hooks/README.md](.claude/hooks/README.md)** ### 📚 I want to add ONE skill Browse the [skills catalog](.claude/skills/) and copy what you need. **Available:** - **backend-dev-guidelines** - Node.js/Express/TypeScript patterns - **frontend-dev-guidelines** - React/TypeScript/MUI v7 patterns - **skill-developer** - Meta-skill for creating skills - **route-tester** - Test authenticated API routes - **error-tracking** - Sentry integration patterns **👉 [Skills Guide: .claude/skills/README.md](.claude/skills/README.md)** ### 🤖 I want specialized agents 10 production-tested agents for complex tasks: - Code architecture review - Refactoring assistance - Documentation generation - Error debugging - And more... **👉 [Agents Guide: .claude/agents/README.md](.claude/agents/README.md)** --- ## What Makes This Different? ### The Auto-Activation Breakthrough **Problem:** Claude Code skills just sit there. You have to remember to use them. **Solution:** UserPromptSubmit hook that: - Analyzes your prompts - Checks file context - Automatically suggests relevant skills - Works via `skill-rules.json` configuration **Result:** Skills activate when you need them, not when you remember them. ### Production-Tested Patterns These aren't theoretical examples - they're extracted from: - ✅ 6 microservices in production - ✅ 50,000+ lines of TypeScript - ✅ React frontend with complex data grids - ✅ Sophisticated workflow engine - ✅ 6 months of daily Claude Code use The patterns work because they solved real problems. ### Modular Skills (500-Line Rule) Large skills hit context limits. The solution: ``` skill-name/ SKILL.md # <500 lines, high-level guide resources/ topic-1.md # <500 lines each topic-2.md topic-3.md ``` **Progressive disclosure:** Claude loads main skill first, loads resources only when needed. --- ## Repository Structure ``` .claude/ ├── skills/ # 5 production skills │ ├── backend-dev-guidelines/ (12 resource files) │ ├── frontend-dev-guidelines/ (11 resource files) │ ├── skill-developer/ (7 resource files) │ ├── route-tester/ │ ├── error-tracking/ │ └── skill-rules.json # Skill activation configuration ├── hooks/ # 6 hooks for automation │ ├── skill-activation-prompt.* (ESSENTIAL) │ ├── post-tool-use-tracker.sh (ESSENTIAL) │ ├── tsc-check.sh (optional, needs customization) │ └── trigger-build-resolver.sh (optional) ├── agents/ # 10 specialized agents │ ├── code-architecture-reviewer.md │ ├── refactor-planner.md │ ├── frontend-error-fixer.md │ └── ... 7 more └── commands/ # 3 slash commands ├── dev-docs.md └── ... dev/ └── active/ # Dev docs pattern examples └── public-infrastructure-repo/ ``` --- ## Component Catalog ### 🎨 Skills (5) | Skill | Lines | Purpose | Best For | |-------|-------|---------|----------| | [**skill-developer**](.claude/skills/skill-developer/) | 426 | Creating and managing skills | Meta-development | | [**backend-dev-guidelines**](.claude/skills/backend-dev-guidelines/) | 304 | Express/Prisma/Sentry patterns | Backend APIs | | [**frontend-dev-guidelines**](.claude/skills/frontend-dev-guidelines/) | 398 | React/MUI v7/TypeScript | React frontends | | [**route-tester**](.claude/skills/route-tester/) | 389 | Testing authenticated routes | API testing | | [**error-tracking**](.claude/skills/error-tracking/) | ~250 | Sentry integration | Error monitoring | **All skills follow the modular pattern** - main file + resource files for progressive disclosure. **👉 [How to integrate skills →](.claude/skills/README.md)** ### 🪝 Hooks (6) | Hook | Type | Essential? | Customization | |------|------|-----------|---------------| | skill-activation-prompt | UserPromptSubmit | ✅ YES | ✅ None needed | | post-tool-use-tracker | PostToolUse | ✅ YES | ✅ None needed | | tsc-check | Stop | ⚠️ Optional | ⚠️ Heavy - monorepo only | | trigger-build-resolver | Stop | ⚠️ Optional | ⚠️ Heavy - monorepo only | | error-handling-reminder | Stop | ⚠️ Optional | ⚠️ Moderate | | stop-build-check-enhanced | Stop | ⚠️ Optional | ⚠️ Moderate | **Start with the two essential hooks** - they enable skill auto-activation and work out of the box. **👉 [Hook setup guide →](.claude/hooks/README.md)** ### 🤖 Agents (10) **Standalone - just copy and use!** | Agent | Purpose | |-------|---------| | code-architecture-reviewer | Review code for architectural consistency | | code-refactor-master | Plan and execute refactoring | | documentation-architect | Generate comprehensive documentation | | frontend-error-fixer | Debug frontend errors | | plan-reviewer | Review development plans | | refactor-planner | Create refactoring strategies | | web-research-specialist | Research technical issues online | | auth-route-tester | Test authenticated endpoints | | auth-route-debugger | Debug auth issues | | auto-error-resolver | Auto-fix TypeScript errors | **👉 [How agents work →](.claude/agents/README.md)** ### 💬 Slash Commands (3) | Command | Purpose | |---------|---------| | /dev-docs | Create structured dev documentation | | /dev-docs-update | Update docs before context reset | | /route-research-for-testing | Research route patterns for testing | --- ## Key Concepts ### Hooks + skill-rules.json = Auto-Activation **The system:** 1. **skill-activation-prompt hook** runs on every user prompt 2. Checks **skill-rules.json** for trigger patterns 3. Suggests relevant skills automatically 4. Skills load only when needed **This solves the #1 problem** with Claude Code skills: they don't activate on their own. ### Progressive Disclosure (500-Line Rule) **Problem:** Large skills hit context limits **Solution:** Modular structure - Main SKILL.md <500 lines (overview + navigation) - Resource files <500 lines each (deep dives) - Claude loads incrementally as needed **Example:** backend-dev-guidelines has 12 resource files covering routing, controllers, services, repositories, testing, etc. ### Dev Docs Pattern **Problem:** Context resets lose project context **Solution:** Three-file structure - `[task]-plan.md` - Strategic plan - `[task]-context.md` - Key decisions and files - `[task]-tasks.md` - Checklist format **Works with:** `/dev-docs` slash command to generate these automatically --- ## ⚠️ Important: What Won't Work As-Is ### settings.json The included `settings.json` is an **example only**: - Stop hooks reference specific monorepo structure - Service names (blog-api, etc.) are examples - MCP servers may not exist in your setup **To use it:** 1. Extract ONLY UserPromptSubmit and PostToolUse hooks 2. Customize or skip Stop hooks 3. Update MCP server list for your setup ### Blog Domain Examples Skills use generic blog examples (Post/Comment/User): - These are **teaching examples**, not requirements - Patterns work for any domain (e-commerce, SaaS, etc.) - Adapt the patterns to your business logic ### Hook Directory Structures Some hooks expect specific structures: - `tsc-check.sh` expects service directories - Customize based on YOUR project layout --- ## Integration Workflow **Recommended approach:** ### Phase 1: Skill Activation (15 min) 1. Copy skill-activation-prompt hook 2. Copy post-tool-use-tracker hook 3. Update settings.json 4. Install hook dependencies ### Phase 2: Add First Skill (10 min) 1. Pick ONE relevant skill 2. Copy skill directory 3. Create/update skill-rules.json 4. Customize path patterns ### Phase 3: Test & Iterate (5 min) 1. Edit a file - skill should activate 2. Ask a question - skill should be suggested 3. Add more skills as needed ### Phase 4: Optional Enhancements - Add agents you find useful - Add slash commands - Customize Stop hooks (advanced) --- ## Getting Help ### For Users **Issues with integration?** 1. Check [CLAUDE_INTEGRATION_GUIDE.md](CLAUDE_INTEGRATION_GUIDE.md) 2. Ask Claude: "Why isn't [skill] activating?" 3. Open an issue with your project structure ### For Claude Code When helping users integrate: 1. **Read CLAUDE_INTEGRATION_GUIDE.md FIRST** 2. Ask about their project structure 3. Customize, don't blindly copy 4. Verify after integration --- ## What This Solves ### Before This Infrastructure ❌ Skills don't activate automatically ❌ Have to remember which skill to use ❌ Large skills hit context limits ❌ Context resets lose project knowledge ❌ No consistency across development ❌ Manual agent invocation every time ### After This Infrastructure ✅ Skills suggest themselves based on context ✅ Hooks trigger skills at the right time ✅ Modular skills stay under context limits ✅ Dev docs preserve knowledge across resets ✅ Consistent patterns via guardrails ✅ Agents streamline complex tasks --- ## Community **Found this useful?** - ⭐ Star this repo - 🐛 Report issues or suggest improvements - 💬 Share your own skills/hooks/agents - 📝 Contribute examples from your domain **Background:** This infrastructure was detailed in a post I made to Reddit ["Claude Code is a Beast – Tips from 6 Months of Hardcore Use"](https://www.reddit.com/r/ClaudeAI/comments/1oivjvm/claude_code_is_a_beast_tips_from_6_months_of/). After hundreds of requests, this showcase was created to help the community implement these patterns. --- ## License MIT License - Use freely in your projects, commercial or personal. --- ## Quick Links - 📖 [Claude Integration Guide](CLAUDE_INTEGRATION_GUIDE.md) - For AI-assisted setup - 🎨 [Skills Documentation](.claude/skills/README.md) - 🪝 [Hooks Setup](.claude/hooks/README.md) - 🤖 [Agents Guide](.claude/agents/README.md) - 📝 [Dev Docs Pattern](dev/README.md) **Start here:** Copy the two essential hooks, add one skill, and see the auto-activation magic happen. ================================================ FILE: dev/README.md ================================================ # Dev Docs Pattern A methodology for maintaining project context across Claude Code sessions and context resets. --- ## The Problem **Context resets lose everything:** - Implementation decisions - Key files and their purposes - Task progress - Technical constraints - Why certain approaches were chosen **After a reset, Claude has to rediscover everything.** --- ## The Solution: Persistent Dev Docs A three-file structure that captures everything needed to resume work: ``` dev/active/[task-name]/ ├── [task-name]-plan.md # Strategic plan ├── [task-name]-context.md # Key decisions & files └── [task-name]-tasks.md # Checklist format ``` **These files survive context resets** - Claude reads them to get back up to speed instantly. --- ## Three-File Structure ### 1. [task-name]-plan.md **Purpose:** Strategic plan for the implementation **Contains:** - Executive summary - Current state analysis - Proposed future state - Implementation phases - Detailed tasks with acceptance criteria - Risk assessment - Success metrics - Timeline estimates **When to create:** At the start of a complex task **When to update:** When scope changes or new phases discovered **Example:** ```markdown # Feature Name - Implementation Plan ## Executive Summary What we're building and why ## Current State Where we are now ## Implementation Phases ### Phase 1: Infrastructure (2 hours) - Task 1.1: Set up database schema - Acceptance: Schema compiles, relationships correct - Task 1.2: Create service structure - Acceptance: All directories created ### Phase 2: Core Functionality (3 hours) ... ``` --- ### 2. [task-name]-context.md **Purpose:** Key information for resuming work **Contains:** - SESSION PROGRESS section (updated frequently!) - What's completed vs in-progress - Key files and their purposes - Important decisions made - Technical constraints discovered - Links to related files - Quick resume instructions **When to create:** Start of task **When to update:** **FREQUENTLY** - after major decisions, completions, or discoveries **Example:** ```markdown # Feature Name - Context ## SESSION PROGRESS (2025-10-29) ### ✅ COMPLETED - Database schema created (User, Post, Comment models) - PostController implemented with BaseController pattern - Sentry integration working ### 🟡 IN PROGRESS - Creating PostService with business logic - File: src/services/postService.ts ### ⚠️ BLOCKERS - Need to decide on caching strategy ## Key Files **src/controllers/PostController.ts** - Extends BaseController - Handles HTTP requests for posts - Delegates to PostService **src/services/postService.ts** (IN PROGRESS) - Business logic for post operations - Next: Add caching ## Quick Resume To continue: 1. Read this file 2. Continue implementing PostService.createPost() 3. See tasks file for remaining work ``` **CRITICAL:** Update the SESSION PROGRESS section every time significant work is done! --- ### 3. [task-name]-tasks.md **Purpose:** Checklist for tracking progress **Contains:** - Phases broken down by logical sections - Tasks in checkbox format - Status indicators (✅/🟡/⏳) - Acceptance criteria - Quick resume section **When to create:** Start of task **When to update:** After completing each task or discovering new tasks **Example:** ```markdown # Feature Name - Task Checklist ## Phase 1: Setup ✅ COMPLETE - [x] Create database schema - [x] Set up controllers - [x] Configure Sentry ## Phase 2: Implementation 🟡 IN PROGRESS - [x] Create PostController - [ ] Create PostService (IN PROGRESS) - [ ] Create PostRepository - [ ] Add validation with Zod ## Phase 3: Testing ⏳ NOT STARTED - [ ] Unit tests for service - [ ] Integration tests - [ ] Manual API testing ``` --- ## When to Use Dev Docs **Use for:** - ✅ Complex multi-day tasks - ✅ Features with many moving parts - ✅ Tasks likely to span multiple sessions - ✅ Work that needs careful planning - ✅ Refactoring large systems **Skip for:** - ❌ Simple bug fixes - ❌ Single-file changes - ❌ Quick updates - ❌ Trivial modifications **Rule of thumb:** If it takes more than 2 hours or spans multiple sessions, use dev docs. --- ## Workflow with Dev Docs ### Starting a New Task 1. **Use /dev-docs slash command:** ``` /dev-docs refactor authentication system ``` 2. **Claude creates the three files:** - Analyzes requirements - Examines codebase - Creates comprehensive plan - Generates context and tasks files 3. **Review and adjust:** - Check if plan makes sense - Add any missing considerations - Adjust timeline estimates ### During Implementation 1. **Refer to plan** for overall strategy 2. **Update context.md** frequently: - Mark completed work - Note decisions made - Add blockers 3. **Check off tasks** in tasks.md as you complete them ### After Context Reset 1. **Claude reads all three files** 2. **Understands complete state** in seconds 3. **Resumes exactly where you left off** No need to explain what you were doing - it's all documented! --- ## Integration with Slash Commands ### /dev-docs **Creates:** New dev docs for a task **Usage:** ``` /dev-docs implement real-time notifications ``` **Generates:** - `dev/active/implement-real-time-notifications/` - implement-real-time-notifications-plan.md - implement-real-time-notifications-context.md - implement-real-time-notifications-tasks.md ### /dev-docs-update **Updates:** Existing dev docs before context reset **Usage:** ``` /dev-docs-update ``` **Updates:** - Marks completed tasks - Adds new tasks discovered - Updates context with session progress - Captures current state **Use when:** Approaching context limits or ending session --- ## File Organization ``` dev/ ├── README.md # This file ├── active/ # Current work │ ├── task-1/ │ │ ├── task-1-plan.md │ │ ├── task-1-context.md │ │ └── task-1-tasks.md │ └── task-2/ │ └── ... └── archive/ # Completed work (optional) └── old-task/ └── ... ``` **active/**: Work in progress **archive/**: Completed tasks (for reference) --- ## Example: Real Usage See **dev/active/public-infrastructure-repo/** in this repository for a real example: - **plan.md** - 700+ line strategic plan for creating this showcase - **context.md** - Tracks what's completed, decisions made, what's next - **tasks.md** - Checklist of all phases and tasks This is the actual dev docs used to build this showcase! --- ## Best Practices ### Update Context Frequently **Bad:** Update only at end of session **Good:** Update after each major milestone **SESSION PROGRESS section should always reflect reality:** ```markdown ## SESSION PROGRESS (YYYY-MM-DD) ### ✅ COMPLETED (list everything done) ### 🟡 IN PROGRESS (what you're working on RIGHT NOW) ### ⚠️ BLOCKERS (what's preventing progress) ``` ### Make Tasks Actionable **Bad:** "Fix the authentication" **Good:** "Implement JWT token validation in AuthMiddleware.ts (Acceptance: Tokens validated, errors to Sentry)" **Include:** - Specific file names - Clear acceptance criteria - Dependencies on other tasks ### Keep Plan Current If scope changes: - Update the plan - Add new phases - Adjust timeline estimates - Note why scope changed --- ## For Claude Code **When user asks to create dev docs:** 1. **Use the /dev-docs slash command** if available 2. **Or create manually:** - Ask about the task scope - Analyze relevant codebase files - Create comprehensive plan - Generate context and tasks 3. **Structure the plan with:** - Clear phases - Actionable tasks - Acceptance criteria - Risk assessment 4. **Make context file resumable:** - SESSION PROGRESS at top - Quick resume instructions - Key files list with explanations **When resuming from dev docs:** 1. **Read all three files** (plan, context, tasks) 2. **Start with context.md** - has current state 3. **Check tasks.md** - see what's done and what's next 4. **Refer to plan.md** - understand overall strategy **Update frequently:** - Mark tasks complete immediately - Update SESSION PROGRESS after significant work - Add new tasks as discovered --- ## Creating Dev Docs Manually If you don't have the /dev-docs command: **1. Create directory:** ```bash mkdir -p dev/active/your-task-name ``` **2. Create plan.md:** - Executive summary - Implementation phases - Detailed tasks - Timeline estimates **3. Create context.md:** - SESSION PROGRESS section - Key files - Important decisions - Quick resume instructions **4. Create tasks.md:** - Phases with checkboxes - [ ] Task format - Acceptance criteria --- ## Benefits **Before dev docs:** - Context reset = start over - Forget why decisions were made - Lose track of progress - Repeat work **After dev docs:** - Context reset = read 3 files, resume instantly - Decisions documented - Progress tracked - No repeated work **Time saved:** Hours per context reset --- ## Next Steps 1. **Try the pattern** on your next complex task 2. **Use /dev-docs** slash command (if available) 3. **Update frequently** - especially context.md 4. **See it in action** - Browse dev/active/public-infrastructure-repo/ **Questions?** See [CLAUDE_INTEGRATION_GUIDE.md](../CLAUDE_INTEGRATION_GUIDE.md)