Ai-agent-skills wednesday-dev
Technical development guidelines for Wednesday Solutions projects. Enforces import ordering, complexity limits, naming conventions, TypeScript best practices, and code quality standards for React/Next.js applications.
git clone https://github.com/wednesday-solutions/ai-agent-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/wednesday-solutions/ai-agent-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/wednesday-dev" ~/.claude/skills/wednesday-solutions-ai-agent-skills-wednesday-dev && rm -rf "$T"
skills/wednesday-dev/SKILL.mdWednesday Technical Development Guidelines
Trigger
Load this skill when writing, reviewing, or refactoring code logic:
- Writing a new function, hook, API route, or service
- Reviewing TypeScript / JavaScript for quality issues
- Refactoring for complexity, naming, or structure
- "Does this follow our coding standards?"
Do NOT use this skill for: choosing UI components or visual styling (use
wednesday-design). This skill covers code structure, TypeScript, imports, and logic — not visual output.
This skill enforces code quality standards for Wednesday Solutions projects. Follow these guidelines to maintain consistency, readability, and maintainability across the codebase.
1. Import Order
Imports must follow a strict ordering pattern. The project uses
@trivago/prettier-plugin-sort-imports to enforce this automatically.
Required Order
// 1. React (always first) import React, { useState, useEffect } from 'react' // 2. Next.js imports import { useRouter } from 'next/navigation' import Image from 'next/image' // 3. State management (Recoil, Redux) import { useSelector } from 'react-redux' // 4. UI libraries (MUI, Radix) import { Button } from '@mui/material' // 5. Path alias imports (@/) import { useAuth } from '@/lib/hooks/useAuth' import { API_ROUTES } from '@/lib/constants' // 6. External packages import { z } from 'zod' import { motion } from 'framer-motion' // 7. Internal components import { Header } from 'components/Header' // 8. Relative imports (sibling/parent) import { utils } from './utils' import { types } from '../types'
Rules
- Each import group must be separated by a blank line
- Imports within each group should be alphabetically sorted
- Use type imports for TypeScript types:
import type { User } from '@/types' - Avoid wildcard imports (
) unless necessaryimport * as
2. Code Complexity
Cyclomatic Complexity
Maximum allowed: 8 (stricter than industry standard of 10)
Functions exceeding this limit must be refactored. See references/COMPLEXITY.md for detailed strategies.
Quick Remediation
| Complexity | Action Required |
|---|---|
| 1-4 | Good - easy to test |
| 5-7 | Acceptable - consider simplifying |
| 8 | At limit - refactor if adding logic |
| 9+ | Must refactor before merging |
How to Reduce Complexity
- Extract helper functions - Break large functions into smaller, focused ones
- Use early returns - Replace nested conditions with guard clauses
- Replace conditionals with polymorphism - Use strategy pattern for complex branching
- Simplify boolean expressions - Extract complex conditions into named variables
- Use lookup tables - Replace switch statements with object maps
// BAD: High complexity function getDiscount(user: User, items: Item[]) { let discount = 0 if (user.isPremium) { if (items.length > 10) { discount = 20 } else if (items.length > 5) { discount = 10 } else { discount = 5 } } else { if (items.length > 10) { discount = 10 } else if (items.length > 5) { discount = 5 } } return discount } // GOOD: Low complexity with lookup table const DISCOUNT_TABLE = { premium: { large: 20, medium: 10, small: 5 }, regular: { large: 10, medium: 5, small: 0 }, } as const function getCartSize(count: number): 'large' | 'medium' | 'small' { if (count > 10) return 'large' if (count > 5) return 'medium' return 'small' } function getDiscount(user: User, items: Item[]) { const tier = user.isPremium ? 'premium' : 'regular' const size = getCartSize(items.length) return DISCOUNT_TABLE[tier][size] }
File Size Limits
- Max file lines: 250
- Max function lines: 350
- Max line length: 120 characters
3. Naming Conventions
Components & Classes
Use PascalCase for React components, classes, types, and interfaces.
// Components function UserProfile() { } function PaymentCard() { } // Types & Interfaces interface UserProfileProps { } type PaymentMethod = 'card' | 'bank' // Classes class AuthService { }
Functions & Variables
Use camelCase for functions, variables, hooks, and object properties.
// Functions function fetchUserData() { } function calculateTotal() { } // Variables const isLoading = true const userProfile = { } // Hooks function useAuth() { } function useLocalStorage() { }
Constants & Enums
Use UPPER_SNAKE_CASE for constants and enum values.
// Constants const API_BASE_URL = 'https://api.example.com' const MAX_RETRY_ATTEMPTS = 3 // Enums enum UserRole { ADMIN = 'ADMIN', USER = 'USER', GUEST = 'GUEST', }
Boolean Variables
Prefix with
is, has, should, can, or will for clarity.
// Good const isAuthenticated = true const hasPermission = false const shouldRefetch = true const canEdit = user.role === 'ADMIN' // Bad const authenticated = true const permission = false const refetch = true
Files & Folders
| Type | Convention | Example |
|---|---|---|
| Component files | PascalCase | |
| Hook files | camelCase with prefix | |
| Utility files | camelCase | |
| Constant files | camelCase | |
| Type files | camelCase | |
| Test files | Match source + | |
| Folders | camelCase | , |
Descriptive Naming
Names should be self-documenting:
// Bad - unclear intent const d = new Date() const arr = users.filter(u => u.a) function proc(x: number) { } // Good - clear intent const currentDate = new Date() const activeUsers = users.filter(user => user.isActive) function processPayment(amount: number) { }
4. TypeScript Best Practices
Strict Type Safety
- Enable strict mode in
tsconfig.json - Avoid
type - useany
and narrow typesunknown - Use explicit return types for public functions
- Leverage discriminated unions for state
// Use discriminated unions type AsyncState<T> = | { status: 'idle' } | { status: 'loading' } | { status: 'success'; data: T } | { status: 'error'; error: Error } // Narrow types properly function processValue(value: unknown) { if (typeof value === 'string') { return value.toUpperCase() } if (typeof value === 'number') { return value.toFixed(2) } throw new Error('Unsupported type') }
Type Imports
Separate type imports from value imports:
import { useState } from 'react' import type { FC, ReactNode } from 'react' import { fetchUser } from '@/lib/api' import type { User, UserRole } from '@/types'
5. React Patterns
Component Structure
Follow this order within components:
- Type definitions (props interface)
- Component function declaration
- Hooks (useState, useEffect, custom hooks)
- Derived state / computations
- Event handlers
- Effects
- Return statement (JSX)
interface UserCardProps { userId: string onSelect?: (user: User) => void } export function UserCard({ userId, onSelect }: UserCardProps) { // 1. Hooks const [isExpanded, setIsExpanded] = useState(false) const { data: user, isLoading } = useUser(userId) // 2. Derived state const displayName = user ? `${user.firstName} ${user.lastName}` : 'Unknown' // 3. Event handlers const handleClick = () => { setIsExpanded(!isExpanded) onSelect?.(user) } // 4. Early returns for loading/error states if (isLoading) return <Skeleton /> if (!user) return null // 5. Main render return ( <Card onClick={handleClick}> <CardTitle>{displayName}</CardTitle> {isExpanded && <CardContent>{user.bio}</CardContent>} </Card> ) }
Use Client Directive
Mark client components explicitly:
'use client' import { useState } from 'react' // ... client-side component
6. Forbidden Patterns
No Console Statements
Console statements are forbidden in production code. Use proper logging utilities.
// Forbidden console.log('debug:', data) console.error('Error:', err) // Use structured logging or remove before commit
No Magic Numbers/Strings
Extract to named constants:
// Bad if (retryCount > 3) { } if (status === 'active') { } // Good const MAX_RETRIES = 3 const STATUS_ACTIVE = 'active' if (retryCount > MAX_RETRIES) { } if (status === STATUS_ACTIVE) { }
No Unused Code
- Remove unused imports (enforced by
)eslint-plugin-unused-imports - Delete commented-out code
- Remove unused variables (prefix with
only if intentionally unused)_
7. Testing Requirements
Unit Tests
- Use Jest with React Testing Library
- Test file naming:
ComponentName.test.tsx - Focus on user behavior, not implementation details
import { render, screen, fireEvent } from '@testing-library/react' import { UserCard } from './UserCard' describe('UserCard', () => { it('displays user name correctly', () => { render(<UserCard userId="123" />) expect(screen.getByText('John Doe')).toBeInTheDocument() }) it('calls onSelect when clicked', () => { const onSelect = jest.fn() render(<UserCard userId="123" onSelect={onSelect} />) fireEvent.click(screen.getByRole('button')) expect(onSelect).toHaveBeenCalledWith(expect.objectContaining({ id: '123' })) }) })
E2E Tests
- Use Playwright for end-to-end tests
- Test file naming:
feature.spec.ts - Test critical user flows
8. Security Considerations
- Sanitize HTML with DOMPurify before rendering
- Validate all user inputs with Zod schemas
- Never expose sensitive data in client-side code
- Use environment variables for secrets
- Implement CSRF protection for forms
9. Performance Guidelines
- Use
for code splittingnext/dynamic - Implement proper loading states
- Memoize expensive computations with
useMemo - Avoid unnecessary re-renders with
React.memo - Use image optimization with
next/image
10. Commenting Guidelines (brownfield-aware)
These conventions are read by the brownfield intelligence pipeline to build module purpose maps, detect tech debt, and generate the reverse PRD. Well-placed comments make the map report significantly more useful — they surface intent that can't be inferred from code structure alone.
Substantive comments (captured as developer intent)
Write comments that explain why, not what. Comments of 8+ words that aren't noise are collected as "developer intent" and used to infer module purpose.
// BAD — too short or states the obvious (filtered out) // increment counter i++ // BAD — noise (filtered out) // eslint-disable-next-line @typescript-eslint/no-explicit-any // @param userId - the user id // GOOD — explains why, ≥8 words (captured) // We batch here because the payment provider rate-limits to 10 req/s per merchant // Using optimistic updates to avoid the 200ms API round-trip on every keystroke // This module owns all JWT lifecycle — issue, refresh, revoke. Nothing else should touch tokens.
Tech debt tags
Use these exact tags — the brownfield scanner recognises them and maps severity:
| Tag | Severity | Use for |
|---|---|---|
| 🔴 high | Broken or incorrect behaviour that needs fixing |
| 🔴 high | Known bug, not yet fixed |
| 🔴 high | Dangerous or wrong — must not ship |
| 🟡 medium | Works but is a deliberate shortcut |
| 🟡 medium | Messy workaround, needs rethinking |
| 🟡 medium | Design concern, not yet a bug |
| 🟡 medium | Planned work, not urgent |
| 🟡 medium | Intentionally temporary, must be removed |
| 🔵 low | Performance opportunity |
| 🔵 low | Performance opportunity |
| 🔵 low | Needs a second pair of eyes |
| ⚪ info | Suggestion, not planned work |
| ⚪ info | Important context for future readers |
| ⚪ info | Kept for backwards compat, do not use |
// FIXME: token refresh fails silently when the network drops mid-request // TODO: extract this into a shared retry utility once the payments module is stable // HACK: Stripe webhook retries mean this handler can run twice — idempotency check is manual // DEPRECATED: use useAuthV2() instead — this will be removed in v3
Module-level header comments
Add a short header at the top of barrel files (
index.ts) or the primary file of a directory.
The brownfield pipeline uses this to infer the module's purpose without LLM calls.
// src/auth/index.ts // // Auth module — owns all identity concerns: JWT issue/refresh/revoke, session management, // and permission checks. No other module should read or write tokens directly. // Business feature: yes (user-facing login, OAuth, SSO flows).
Graph annotation comments
When a file uses dynamic patterns that the static analyser cannot resolve, add explicit annotations so the dependency graph stays accurate:
// @wednesday-skills:connects-to login → src/auth/session.ts // (dynamic require resolved manually) const mod = require(`./handlers/${eventName}`) // @wednesday-skills:global db → src/lib/db/client.ts // (injected via app context, not imported directly) app.locals.db = createDbClient()
What NOT to write (filtered as noise)
The following are intentionally ignored by the scanner — don't rely on them for documentation:
// @param userId - the user id ← JSDoc parameters // @returns Promise<User> ← JSDoc return types // eslint-disable-next-line ← linter directives // prettier-ignore ← formatter directives // Copyright 2024 Wednesday Solutions ← copyright headers // Auto-generated by codegen ← generated file markers // do not edit ← generated file markers
For detailed examples and edge cases, see references/COMPLEXITY.md.