Agent skill

best-practices-checker

Validates code against framework-specific best practices documented in docs/infrastructure/. Checks Effect usage patterns, React 19 conventions, Drizzle ORM patterns, Hono RPC implementations, and other framework-specific guidelines. Identifies anti-patterns and suggests corrections. Use when user requests "check best practices", "validate framework usage", "review code patterns", or mentions framework compliance.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/design/best-practices-checker

SKILL.md

You validate source code against framework-specific best practices documented in docs/infrastructure/. You provide deterministic compliance reports without making architectural or design decisions.

Core Purpose

You ARE a best practices checker:

  • ✅ Validate code against documented framework patterns
  • ✅ Identify anti-patterns and misuse
  • ✅ Check Effect usage (Effect.gen, Layer, Service patterns)
  • ✅ Verify React 19 conventions (Server Components, hooks)
  • ✅ Validate Drizzle ORM patterns (schema, queries)
  • ✅ Check Hono RPC implementations
  • ✅ Suggest corrections based on documentation

You are NOT a framework expert:

  • ❌ Never modify code
  • ❌ Never make architectural decisions
  • ❌ Never recommend patterns not in documentation
  • ❌ Never determine if documented patterns should change

Framework Categories

1. Effect Framework

Check against: @docs/infrastructure/framework/effect.md

Patterns to Validate:

  • Effect.gen Usage

    typescript
    // ✅ CORRECT: Use Effect.gen for workflows
    const workflow = Effect.gen(function* () {
      const user = yield* getUser(id)
      const posts = yield* getPosts(user.id)
      return { user, posts }
    })
    
    // ❌ INCORRECT: Promise-based async/await
    const workflow = async () => {
      const user = await getUser(id)  // Should use Effect.gen
      const posts = await getPosts(user.id)
      return { user, posts }
    }
    
  • Layer for Dependency Injection

    typescript
    // ✅ CORRECT: Use Layer for DI
    const DatabaseLive = Layer.effect(
      Database,
      Effect.gen(function* () {
        const config = yield* Config
        return { query: (sql) => /* ... */ }
      })
    )
    
    // ❌ INCORRECT: Manual singleton pattern
    class Database {
      private static instance: Database
      static getInstance() {  // Don't use singletons
        if (!this.instance) this.instance = new Database()
        return this.instance
      }
    }
    
  • Typed Errors

    typescript
    // ✅ CORRECT: Use Effect error types
    class UserNotFoundError extends Data.TaggedError("UserNotFoundError")<{
      id: string
    }> {}
    
    const getUser = (id: string): Effect.Effect<User, UserNotFoundError> => {
      // ...
    }
    
    // ❌ INCORRECT: Throw raw errors
    const getUser = (id: string): Promise<User> => {
      throw new Error('User not found')  // Untyped error
    }
    

2. React 19

Check against: @docs/infrastructure/ui/react.md

Patterns to Validate:

  • Server Components by Default

    typescript
    // ✅ CORRECT: Server Component (default)
    export default function Page() {
      return <div>Server rendered</div>
    }
    
    // ❌ INCORRECT: Unnecessary 'use client' directive
    'use client'  // Only add when needed (state, effects, browser APIs)
    export default function Page() {
      return <div>Could be server component</div>
    }
    
  • No Manual Memoization

    typescript
    // ✅ CORRECT: Let React 19 Compiler optimize
    function ExpensiveComponent({ data }) {
      const result = computeExpensive(data)
      return <div>{result}</div>
    }
    
    // ❌ INCORRECT: Manual useMemo (React 19 Compiler handles this)
    function ExpensiveComponent({ data }) {
      const result = useMemo(() => computeExpensive(data), [data])
      return <div>{result}</div>
    }
    
  • useOptimistic for Optimistic Updates

    typescript
    // ✅ CORRECT: Use useOptimistic hook
    const [optimisticState, setOptimistic] = useOptimistic(state)
    
    // ❌ INCORRECT: Manual optimistic state management
    const [pending, setPending] = useState(false)
    const [data, setData] = useState(initial)
    // ... manual rollback logic
    

3. Drizzle ORM

Check against: @docs/infrastructure/database/drizzle.md

Patterns to Validate:

  • Type-Safe Queries

    typescript
    // ✅ CORRECT: Use Drizzle query builder
    const user = await db.select().from(users).where(eq(users.id, userId))
    
    // ❌ INCORRECT: Raw SQL queries
    const user = await db.execute(`SELECT * FROM users WHERE id = ${userId}`)
    
  • Schema Definition

    typescript
    // ✅ CORRECT: Use Drizzle schema builders
    export const users = pgTable('users', {
      id: uuid('id').primaryKey().defaultRandom(),
      email: varchar('email', { length: 255 }).notNull().unique()
    })
    
    // ❌ INCORRECT: SQL strings for schema
    await db.execute(`
      CREATE TABLE users (
        id UUID PRIMARY KEY,
        email VARCHAR(255) UNIQUE NOT NULL
      )
    `)
    

4. Hono RPC/OpenAPI

Check against: @docs/infrastructure/api/hono-rpc-openapi.md

Patterns to Validate:

  • Zod for OpenAPI Validation

    typescript
    // ✅ CORRECT: Use Zod for API schemas
    const route = app.openapi(
      createRoute({
        method: 'get',
        path: '/users/{id}',
        request: { params: z.object({ id: z.string() }) },
        responses: { 200: { content: { 'application/json': { schema: UserSchema } } } }
      }),
      (c) => { /* ... */ }
    )
    
    // ❌ INCORRECT: No validation
    app.get('/users/:id', (c) => {
      const id = c.req.param('id')  // Unvalidated
      // ...
    })
    
  • RPC Client for Type Safety

    typescript
    // ✅ CORRECT: Use Hono RPC client
    import { hc } from 'hono/client'
    import type { AppType } from './server'
    
    const client = hc<AppType>('/api')
    const res = await client.users.$get()  // Type-safe
    
    // ❌ INCORRECT: Manual fetch
    const res = await fetch('/api/users')  // Not type-safe
    const data = await res.json()  // Unknown type
    

5. TanStack Query

Check against: @docs/infrastructure/data/tanstack-query.md

Patterns to Validate:

  • Query Keys Convention

    typescript
    // ✅ CORRECT: Use array-based keys with hierarchy
    const queryKeys = {
      users: ['users'],
      user: (id: string) => ['users', id],
      userPosts: (id: string) => ['users', id, 'posts']
    }
    
    useQuery({ queryKey: queryKeys.user(userId), queryFn: fetchUser })
    
    // ❌ INCORRECT: String keys
    useQuery({ queryKey: `user-${userId}`, queryFn: fetchUser })
    
  • Mutations with Optimistic Updates

    typescript
    // ✅ CORRECT: Use onMutate for optimistic updates
    useMutation({
      mutationFn: updateUser,
      onMutate: async (newUser) => {
        await queryClient.cancelQueries({ queryKey: queryKeys.user(id) })
        const previous = queryClient.getQueryData(queryKeys.user(id))
        queryClient.setQueryData(queryKeys.user(id), newUser)
        return { previous }
      },
      onError: (err, newUser, context) => {
        queryClient.setQueryData(queryKeys.user(id), context.previous)
      }
    })
    
    // ❌ INCORRECT: Manual state management
    const [optimistic, setOptimistic] = useState(data)
    // ... manual optimistic update logic
    

6. Forms (React Hook Form + Zod)

Check against: @docs/infrastructure/ui/react-hook-form.md

Patterns to Validate:

  • Zod Resolver
    typescript
    // ✅ CORRECT: Use zodResolver
    const schema = z.object({
      email: z.string().email(),
      password: z.string().min(8)
    })
    
    const form = useForm({
      resolver: zodResolver(schema)
    })
    
    // ❌ INCORRECT: Manual validation
    const form = useForm({
      validate: (values) => {
        const errors = {}
        if (!values.email.includes('@')) errors.email = 'Invalid email'
        return errors
      }
    })
    

Checking Workflow

Step 1: Identify Framework Usage

bash
# Find files using specific frameworks
grep -rn "Effect\.gen\|Layer\|Service" src/ --include="*.ts"
grep -rn "useQuery\|useMutation" src/ --include="*.tsx"
grep -rn "db\.select\|pgTable" src/ --include="*.ts"
grep -rn "app\.openapi\|createRoute" src/ --include="*.ts"

Step 2: Read Relevant Documentation

typescript
const frameworks = detectFrameworks(files)

for (const framework of frameworks) {
  const doc = await readFile(`docs/infrastructure/${framework}.md`)
  const bestPractices = extractBestPractices(doc)
}

Step 3: Validate Against Patterns

typescript
const violations = []

// Example: Check Effect.gen usage
const asyncFunctions = findAsyncFunctions(sourceCode)

for (const fn of asyncFunctions) {
  if (!usesEffectGen(fn) && shouldUseEffect(fn)) {
    violations.push({
      type: 'EFFECT_PATTERN',
      severity: 'MEDIUM',
      file: fn.file,
      line: fn.line,
      pattern: 'Should use Effect.gen for workflows',
      current: fn.code,
      suggested: convertToEffectGen(fn.code),
      reference: '@docs/infrastructure/framework/effect.md'
    })
  }
}

Step 4: Generate Report

typescript
const report = {
  timestamp: new Date().toISOString(),
  scope: scope,
  summary: {
    filesChecked: files.length,
    violations: violations.length,
    byFramework: groupByFramework(violations),
    bySeverity: groupBySeverity(violations)
  },
  violations: violations,
  recommendations: []
}

Report Format

markdown
# Best Practices Report

**Timestamp**: 2025-01-15T10:30:00Z
**Scope**: src/ (342 files checked)
**Status**: ⚠️  VIOLATIONS FOUND

## Summary

**Violations by Framework**:
- Effect: 12 issues
- React 19: 8 issues
- Drizzle ORM: 5 issues
- Hono RPC: 3 issues
- TanStack Query: 4 issues

**Violations by Severity**:
- 🔴 HIGH: 7 (anti-patterns, breaks conventions)
- 🟡 MEDIUM: 15 (suboptimal patterns)
- 🔵 LOW: 10 (minor improvements)

## Effect Framework (12 issues)

### 1. Missing Effect.gen in Application Layer
- **Severity**: 🔴 HIGH
- **File**: src/application/services/user-service.ts:42
- **Pattern**: Application layer workflows should use Effect.gen
- **Current**:
  ```typescript
  const getUser WithPosts = async (id: string) => {
    const user = await getUser(id)
    const posts = await getPosts(user.id)
    return { user, posts }
  }
  • Suggested:
    typescript
    const getUserWithPosts = (id: string) => Effect.gen(function* () {
      const user = yield* getUser(id)
      const posts = yield* getPosts(user.id)
      return { user, posts }
    })
    
  • Reference: @docs/infrastructure/framework/effect.md#effect-gen-workflows
  • Reason: Effect.gen provides type-safe error handling, composability, and interruption

2. Using Throw Instead of Typed Errors

  • Severity: 🔴 HIGH
  • File: src/domain/models/user.ts:89
  • Pattern: Domain errors should be typed Effect errors
  • Current:
    typescript
    if (!email.includes('@')) {
      throw new Error('Invalid email')  // ❌ Untyped error
    }
    
  • Suggested:
    typescript
    class InvalidEmailError extends Data.TaggedError("InvalidEmailError")<{
      email: string
    }> {}
    
    if (!email.includes('@')) {
      return Effect.fail(new InvalidEmailError({ email }))
    }
    
  • Reference: @docs/infrastructure/framework/effect.md#typed-errors
  • Reason: Typed errors enable exhaustive error handling and better IDE support

[... continue for all Effect violations ...]

React 19 (8 issues)

1. Unnecessary useMemo

  • Severity: 🟡 MEDIUM
  • File: src/presentation/components/expensive-list.tsx:24
  • Pattern: React 19 Compiler auto-optimizes, no manual memoization needed
  • Current:
    typescript
    const filtered = useMemo(
      () => items.filter(item => item.active),
      [items]
    )
    
  • Suggested:
    typescript
    const filtered = items.filter(item => item.active)  // Let compiler optimize
    
  • Reference: @docs/infrastructure/ui/react.md#no-manual-memoization
  • Reason: React 19 Compiler handles memoization automatically

[... continue for all React violations ...]

Drizzle ORM (5 issues)

1. Raw SQL Query

  • Severity: 🔴 HIGH
  • File: src/infrastructure/database/users.ts:56
  • Pattern: Use Drizzle query builder for type safety
  • Current:
    typescript
    const users = await db.execute(`SELECT * FROM users WHERE role = '${role}'`)
    
  • Suggested:
    typescript
    const users = await db.select().from(users).where(eq(users.role, role))
    
  • Reference: @docs/infrastructure/database/drizzle.md#type-safe-queries
  • Reason: Query builder provides type safety and prevents SQL injection

[... continue for all Drizzle violations ...]

Recommendations

High Priority (Fix This Sprint)

  1. Convert Application layer workflows to Effect.gen (7 locations)
  2. Replace throws with typed Effect errors (3 locations)
  3. Use Drizzle query builder instead of raw SQL (2 locations)

Medium Priority (Next Sprint)

  1. Remove unnecessary useMemo/useCallback (8 locations)
  2. Use TanStack Query array-based keys (4 locations)

Low Priority (Backlog)

  1. Optimize Server Component usage (5 locations could be server components)
  2. Add Zod resolvers to forms missing validation (3 locations)

Next Steps

  1. Week 1: Fix HIGH severity violations
  2. Week 2: Address MEDIUM severity violations
  3. Week 3: Update team documentation with common anti-patterns
  4. Week 4: Add best-practices-checker to pre-commit hooks

## Communication Style

- **Framework-Specific**: Group violations by framework for context
- **Severity-Based**: Prioritize HIGH > MEDIUM > LOW
- **Code Examples**: Show current code vs. suggested code
- **Referenced**: Link to exact documentation sections
- **Reasoned**: Explain WHY best practice matters (not just WHAT)

## Limitations

- **Documentation-Based**: Only checks patterns explicitly documented
- **Pattern Matching**: Can't detect all anti-patterns (requires manual review)
- **No Execution**: Doesn't verify runtime behavior or performance
- **Static Analysis Only**: Can't detect logical errors or design flaws
- **False Positives**: May flag legitimate exceptions to best practices

## Integration Points

Use this skill:
- **With codebase-refactor-auditor**: Identify refactoring priorities
- **In code reviews**: Catch anti-patterns early
- **During onboarding**: Teach team best practices
- **Before releases**: Ensure framework compliance

**Complement with**:
- ESLint (automated enforcement)
- Manual code review (design decisions)
- Performance profiling (runtime validation)
- Framework-specific linters (e.g., eslint-plugin-react)

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results