agentleFS
hoangNguyenAngelhackhoangNguyenAngelhack/ai-agent-setup/AGENTS.md

ai-agent-setup

AGENTS.md3 starsChanged 6 months agoMITView on GitHub

Auto-generated from .agents/skills/ — DO NOT EDIT MANUALLY Run node scripts/compile-skills.js to regenerate Version: 1.0.0 Comprehensive code review framework for production-quality code.

  • Reads credentials
  • Installs packages

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

18 rules. About 1 is common, 10 are distinctive, and 7 are very specific to this project.

  • ≈1

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 7

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Common:

    Never hardcode secrets - use environment variables

    line 781
  2. Distinctive:

    Use parameterized queries (Prisma handles this)

    line 784
  3. Very specific:

    Use Helmet.js for HTTP security headers

    line 786
  4. Distinctive:

    Use TypeScript with strict mode

    line 978
  5. Very specific:

    Prefer const over let

    line 979
  6. Distinctive:

    Use named exports for components

    line 980
  7. Distinctive:

    Keep components small and focused

    line 981
  8. Distinctive:

    Use CSS-in-JS sparingly (prefer Tailwind)

    line 983
  9. Distinctive:

    Test user interactions, not implementation details

    line 989
  10. Very specific:

    Use FlatList for long lists (not ScrollView)

    line 1221
  11. Very specific:

    Use useMemo for expensive calculations

    line 1222
  12. Very specific:

    Use expo-image or FastImage for images

    line 1223
  13. Very specific:

    Use react-native-reanimated for 60fps animations

    line 1224
  14. Very specific:

    Avoid re-renders with memo() for heavy components

    line 1225
  15. Distinctive:

    Test user flows, not implementation

    line 1246
  16. Distinctive:

    Write tests before code using the RED-GREEN-REFACTOR cycle.

    line 1257
  17. Distinctive:

    Write a test for the behavior you want.

    line 1281
  18. Distinctive:

    Write the minimal code to make the test pass.

    line 1302

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

Agent Skills

Auto-generated from .agents/skills/ — DO NOT EDIT MANUALLY

Run node scripts/compile-skills.js to regenerate

Table of Contents

  • code-review
  • nestjs-backend
  • nodejs-backend
  • react-frontend
  • react-native-mobile
  • tdd

Skills Overview

SkillDescription
code-reviewPerform five-axis code reviews covering correctness, readability, architecture, security, and performance. Use when reviewing PRs, code changes, or evaluating code quality.
nestjs-backendBuild production-ready NestJS backend APIs with TypeORM/Prisma, dependency injection, guards, and microservices patterns. Use when creating NestJS APIs, modules, services, controllers, or enterprise-grade backends.
nodejs-backendBuild production-ready Node.js backend APIs with Express, Prisma, Redis, and BullMQ. Use when creating REST APIs, database models, services, middleware, background jobs, or server-side logic.
react-frontendBuild React applications with Next.js or Vite, TailwindCSS, shadcn/ui, Zustand, and TanStack Query. Use when creating web UI components, pages, forms, state management, or data fetching.
react-native-mobileBuild React Native mobile apps with Expo or React Native CLI, NativeWind (Tailwind), React Navigation, and Zustand. Use when creating iOS/Android apps, mobile UI, navigation, or native features.
tddTest-Driven Development workflow with RED-GREEN-REFACTOR cycle. Use when writing tests first, implementing features with TDD, or ensuring code correctness through testing.

code-review

Version: 1.0.0

Five-Axis Code Review

Comprehensive code review framework for production-quality code.

Impact Levels

LevelDescription
CRITICALMust fix - blocks merge
MAJORShould fix - significant issue
MINORNice to fix - code quality
NITOptional - style preference

The Five Axes

1. Correctness (CRITICAL)

  • Does the code work as intended?
  • Are edge cases handled?
  • Does it match the specification/requirements?
  • Are there potential bugs or logic errors?
Questions to ask:
- What happens with null/undefined inputs?
- What happens with empty arrays/objects?
- Are boundary conditions handled?
- Is error handling comprehensive?

2. Readability (MAJOR)

  • Can others understand this code easily?
  • Are names descriptive and consistent?
  • Is the code structure logical?
  • Are complex parts documented?
Questions to ask:
- Would a new team member understand this?
- Are functions focused and well-named?
- Is there unnecessary complexity?
- Are comments explaining WHY, not WHAT?

3. Architecture (MAJOR)

  • Does it follow established patterns?
  • Are abstractions appropriate?
  • Is the code modular and reusable?
  • Are dependencies managed well?
Questions to ask:
- Does this belong in this layer/module?
- Is there code duplication?
- Are interfaces clean and minimal?
- Does it violate SOLID principles?

4. Security (CRITICAL)

  • Is user input validated?
  • Are secrets properly handled?
  • Is authentication/authorization correct?
  • Are there injection vulnerabilities?
Questions to ask:
- Can user input cause harm?
- Are passwords/tokens exposed in logs?
- Is access control enforced?
- Are SQL queries parameterized?

5. Performance (MAJOR)

  • Are there N+1 queries?
  • Is pagination used for lists?
  • Are expensive operations cached?
  • Are there memory leaks?
Questions to ask:
- How does this scale with data size?
- Are database queries optimized?
- Is caching appropriate?
- Are async operations handled correctly?

Review Process

Step 1: Understand Context

- What problem does this PR solve?
- What's the expected behavior?
- Are there related issues/PRs?

Step 2: Review Changes

For each file:
1. Understand the purpose of changes
2. Check each axis
3. Note issues with severity (critical/major/minor/nit)

Step 3: Provide Feedback

Format:
[SEVERITY] File:Line - Description

Examples:
[CRITICAL] auth.ts:45 - SQL injection vulnerability
[MAJOR] user-service.ts:23 - Missing error handling
[MINOR] utils.ts:12 - Variable name could be clearer
[NIT] config.ts:5 - Inconsistent spacing

Common Issues Checklist

Security

  • No hardcoded secrets
  • Input validation present
  • Auth checks on protected routes
  • No sensitive data in logs
  • SQL injection prevented

Performance

  • No N+1 queries
  • Pagination for lists
  • Appropriate caching
  • No memory leaks
  • Async operations correct

Code Quality

  • Functions < 30 lines
  • Clear naming
  • No code duplication
  • Error handling present
  • Types are specific (no any)

Testing

  • Tests cover happy path
  • Tests cover error cases
  • Tests are maintainable
  • No flaky tests

Review Output Format

## Summary
[1-2 sentence overview of the changes]

## Correctness
- [Findings]

## Readability
- [Findings]

## Architecture
- [Findings]

## Security
- [Findings]

## Performance
- [Findings]

## Verdict
[ ] Approve
[ ] Request Changes
[ ] Comment

## Action Items
1. [Critical issues to fix]
2. [Major issues to address]

nestjs-backend

Version: 1.0.0

NestJS Backend Development

Build enterprise-grade backend APIs with NestJS framework.

Tech Stack

  • Framework: NestJS 10+
  • ORM: TypeORM / Prisma
  • Database: PostgreSQL
  • Cache: Redis (@nestjs/cache-manager)
  • Queue: BullMQ (@nestjs/bullmq)
  • Validation: class-validator + class-transformer
  • Documentation: Swagger (@nestjs/swagger)

Project Structure

src/
├── common/              # Shared utilities
│   ├── decorators/      # Custom decorators
│   ├── filters/         # Exception filters
│   ├── guards/          # Auth guards
│   ├── interceptors/    # Logging, transform
│   └── pipes/           # Validation pipes
├── config/              # Configuration module
├── modules/
│   ├── auth/            # Authentication
│   ├── users/           # User module
│   └── [feature]/       # Feature modules
├── app.module.ts        # Root module
└── main.ts              # Bootstrap

Core Patterns

Module Structure (CRITICAL)

// users/users.module.ts
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService, UsersRepository],
  exports: [UsersService], // Export for other modules
})
export class UsersModule {}

Dependency Injection (CRITICAL)

// CORRECT - Constructor injection
@Injectable()
export class UsersService {
  constructor(
    private readonly usersRepository: UsersRepository,
    private readonly cacheManager: Cache,
  ) {}
}

// WRONG - Don't use @Inject() for class providers
constructor(@Inject(UsersRepository) repo: UsersRepository) {}

// OK - Use @Inject() only for non-class tokens
constructor(@Inject('CONFIG') config: AppConfig) {}

Controller Pattern (HIGH)

@Controller('users')
@ApiTags('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Get(':id')
  @ApiOperation({ summary: 'Get user by ID' })
  @ApiResponse({ status: 200, type: UserResponseDto })
  async findOne(@Param('id', ParseUUIDPipe) id: string): Promise<UserResponseDto> {
    return this.usersService.findOne(id);
  }

  @Post()
  @UseGuards(JwtAuthGuard)
  @ApiBearerAuth()
  async create(@Body() dto: CreateUserDto): Promise<UserResponseDto> {
    return this.usersService.create(dto);
  }
}

Service Pattern (HIGH)

@Injectable()
export class UsersService {
  constructor(
    private readonly usersRepository: UsersRepository,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = await this.usersRepository.create(dto);
    
    // Emit domain event
    this.eventEmitter.emit('user.created', new UserCreatedEvent(user));
    
    return user;
  }

  async findOne(id: string): Promise<User> {
    const user = await this.usersRepository.findById(id);
    if (!user) {
      throw new NotFoundException(`User ${id} not found`);
    }
    return user;
  }
}

Repository Pattern (HIGH)

@Injectable()
export class UsersRepository {
  constructor(
    @InjectRepository(User)
    private readonly repo: Repository<User>,
  ) {}

  async findById(id: string): Promise<User | null> {
    return this.repo.findOne({ where: { id } });
  }

  async create(dto: CreateUserDto): Promise<User> {
    const user = this.repo.create(dto);
    return this.repo.save(user);
  }
}

Validation (CRITICAL)

DTOs with class-validator

export class CreateUserDto {
  @IsEmail()
  @ApiProperty({ example: 'user@example.com' })
  email: string;

  @IsString()
  @MinLength(8)
  @Matches(/^(?=.*[A-Za-z])(?=.*\d)/, {
    message: 'Password must contain letters and numbers',
  })
  password: string;

  @IsOptional()
  @IsString()
  name?: string;
}

// Enable validation globally in main.ts
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,           // Strip non-whitelisted properties
  forbidNonWhitelisted: true, // Throw on extra properties
  transform: true,           // Auto-transform to DTO instances
}));

Error Handling (CRITICAL)

Global Exception Filter

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    
    let status = 500;
    let message = 'Internal server error';
    let code = 'INTERNAL_ERROR';

    if (exception instanceof HttpException) {
      status = exception.getStatus();
      const res = exception.getResponse();
      message = typeof res === 'string' ? res : (res as any).message;
      code = (res as any).code || 'HTTP_ERROR';
    }

    response.status(status).json({
      success: false,
      error: { code, message },
      timestamp: new Date().toISOString(),
    });
  }
}

Custom Exceptions

export class BusinessException extends HttpException {
  constructor(message: string, code: string, status: number = 400) {
    super({ message, code }, status);
  }
}

// Usage
throw new BusinessException('Insufficient balance', 'INSUFFICIENT_BALANCE', 422);

Authentication & Authorization (CRITICAL)

JWT Guard

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
  handleRequest(err: any, user: any, info: any) {
    if (err || !user) {
      throw new UnauthorizedException('Invalid or expired token');
    }
    return user;
  }
}

Role-Based Access

// roles.decorator.ts
export const Roles = (...roles: Role[]) => SetMetadata('roles', roles);

// roles.guard.ts
@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const requiredRoles = this.reflector.getAllAndOverride<Role[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);
    if (!requiredRoles) return true;
    
    const { user } = context.switchToHttp().getRequest();
    return requiredRoles.some((role) => user.roles?.includes(role));
  }
}

// Usage
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
async createAdmin(@Body() dto: CreateUserDto) {}

Caching (HIGH)

@Injectable()
export class UsersService {
  constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}

  async findOne(id: string): Promise<User> {
    const cacheKey = `user:${id}`;
    
    // Check cache first
    const cached = await this.cacheManager.get<User>(cacheKey);
    if (cached) return cached;
    
    // Fetch from DB
    const user = await this.usersRepository.findById(id);
    if (user) {
      await this.cacheManager.set(cacheKey, user, 3600000); // 1 hour
    }
    
    return user;
  }
}

Queue / Background Jobs (HIGH)

// email.processor.ts
@Processor('email')
export class EmailProcessor {
  @Process('send-welcome')
  async handleWelcomeEmail(job: Job<{ email: string; name: string }>) {
    await this.mailService.sendWelcome(job.data.email, job.data.name);
  }
}

// Usage in service
@Injectable()
export class UsersService {
  constructor(@InjectQueue('email') private emailQueue: Queue) {}

  async create(dto: CreateUserDto): Promise<User> {
    const user = await this.usersRepository.create(dto);
    
    // Add job to queue
    await this.emailQueue.add('send-welcome', {
      email: user.email,
      name: user.name,
    });
    
    return user;
  }
}

Configuration (HIGH)

// config/database.config.ts
export default registerAs('database', () => ({
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT, 10) || 5432,
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_NAME,
}));

// Usage
@Injectable()
export class DatabaseService {
  constructor(
    @Inject(databaseConfig.KEY)
    private dbConfig: ConfigType<typeof databaseConfig>,
  ) {
    console.log(this.dbConfig.host);
  }
}

Testing (HIGH)

describe('UsersService', () => {
  let service: UsersService;
  let repository: MockType<UsersRepository>;

  beforeEach(async () => {
    const module = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: UsersRepository,
          useFactory: () => ({
            findById: jest.fn(),
            create: jest.fn(),
          }),
        },
      ],
    }).compile();

    service = module.get(UsersService);
    repository = module.get(UsersRepository);
  });

  it('should return user when found', async () => {
    const mockUser = { id: '1', email: 'test@test.com' };
    repository.findById.mockResolvedValue(mockUser);

    const result = await service.findOne('1');

    expect(result).toEqual(mockUser);
    expect(repository.findById).toHaveBeenCalledWith('1');
  });

  it('should throw NotFoundException when user not found', async () => {
    repository.findById.mockResolvedValue(null);

    await expect(service.findOne('1')).rejects.toThrow(NotFoundException);
  });
});

Microservices (MEDIUM)

// TCP Microservice
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
  AppModule,
  {
    transport: Transport.TCP,
    options: { host: '0.0.0.0', port: 3001 },
  },
);

// Message patterns
@MessagePattern({ cmd: 'get_user' })
async getUser(@Payload() data: { id: string }) {
  return this.usersService.findOne(data.id);
}

// Client usage
@Injectable()
export class OrdersService {
  constructor(@Inject('USERS_SERVICE') private usersClient: ClientProxy) {}

  async createOrder(userId: string) {
    const user = await firstValueFrom(
      this.usersClient.send({ cmd: 'get_user' }, { id: userId })
    );
  }
}

Impact Levels

LevelDescription
CRITICALMust follow - security, data integrity
HIGHShould follow - maintainability, performance
MEDIUMRecommended - code quality

nodejs-backend

Version: 1.0.0

Node.js Backend Development

Build production-ready backend APIs following industry best practices.

Impact Levels

LevelDescription
CRITICALMust follow - security, data integrity
HIGHShould follow - maintainability, performance
MEDIUMRecommended - code quality

Tech Stack

  • Framework: Express.js + TypeScript
  • ORM: Prisma
  • Database: PostgreSQL
  • Cache: Redis (ioredis)
  • Queue: BullMQ
  • Validation: Zod
  • Logging: Pino

Project Structure

src/
├── config/          # Configuration (db, redis, env)
├── controllers/     # Route handlers (thin layer)
├── middleware/      # Auth, error handler, logging
├── repositories/    # Data access layer
├── routes/          # Route definitions
├── services/        # Business logic
├── utils/           # Helpers (AppError, logger)
└── index.ts         # Entry point

Patterns

Layered Architecture (HIGH)

Request → Routes → Middleware → Controllers → Services → Repositories → Database

Error Handling (CRITICAL)

// Use AppError for operational errors
class AppError extends Error {
  constructor(
    message: string,
    public statusCode: number = 500,
    public code: string = 'INTERNAL_ERROR'
  ) {
    super(message);
    this.name = 'AppError';
  }
}

// Throw in services
if (!user) throw new AppError('User not found', 404, 'USER_NOT_FOUND');

API Response Format

// Success
{ "success": true, "data": { ... } }

// Error
{ "success": false, "error": { "code": "...", "message": "..." } }

// Paginated
{ "success": true, "data": [...], "pagination": { "page": 1, "total": 100 } }

Database (Prisma) (HIGH)

// Always use transactions for multi-step operations
await db.$transaction(async (tx) => {
  const order = await tx.order.create({ data: orderData });
  await tx.inventory.update({ 
    where: { id: productId }, 
    data: { stock: { decrement: 1 } } 
  });
  return order;
});

// Select only needed fields
const user = await db.user.findUnique({
  where: { id },
  select: { id: true, email: true, name: true }
});

Caching (Redis) (HIGH)

async function getOrSet<T>(
  key: string,
  fetcher: () => Promise<T>,
  ttlSeconds = 3600
): Promise<T> {
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);
  
  const data = await fetcher();
  await redis.setex(key, ttlSeconds, JSON.stringify(data));
  return data;
}

Queue (BullMQ) (MEDIUM)

// Producer
const emailQueue = new Queue('email', { connection: redis });
await emailQueue.add('send-welcome', { to: user.email, name: user.name });

// Consumer
const worker = new Worker('email', async (job) => {
  await sendEmail(job.data);
}, { connection: redis });

Security Rules (CRITICAL)

  • Never hardcode secrets - use environment variables
  • Hash passwords with bcrypt (12+ rounds)
  • Validate all inputs with Zod
  • Use parameterized queries (Prisma handles this)
  • Implement rate limiting on auth endpoints
  • Use Helmet.js for HTTP security headers

Testing (HIGH)

  • 80% minimum test coverage
  • Unit tests for services
  • Integration tests for routes
  • Use Vitest + Supertest

react-frontend

Version: 1.0.0

React Frontend Development

Build modern React applications with best practices.

Impact Levels

LevelDescription
CRITICALMust follow - security, data integrity
HIGHShould follow - maintainability, performance
MEDIUMRecommended - code quality

Framework Selection (HIGH)

Use CaseFramework
Landing pages, SEO, marketingNext.js 14 (App Router)
Admin panels, dashboards, SPAsReact + Vite

Tech Stack

  • Styling: TailwindCSS + shadcn/ui
  • State: Zustand (client) + TanStack Query (server)
  • Forms: react-hook-form + Zod
  • Routing: Next.js App Router or React Router

Project Structure (Vite SPA)

src/
├── components/
│   ├── ui/              # shadcn/ui components
│   └── features/        # Feature-specific components
├── pages/               # Route pages
├── hooks/               # Custom hooks
├── stores/              # Zustand stores
├── lib/                 # Utils, API client
└── types/               # TypeScript types

Patterns

State Management (Zustand) (HIGH)

import { create } from 'zustand';

interface AuthState {
  user: User | null;
  token: string | null;
  setAuth: (user: User, token: string) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>((set) => ({
  user: null,
  token: null,
  setAuth: (user, token) => set({ user, token }),
  logout: () => {
    localStorage.removeItem('token');
    set({ user: null, token: null });
  },
}));

Data Fetching (TanStack Query) (HIGH)

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Query
export function useUsers() {
  return useQuery({
    queryKey: ['users'],
    queryFn: () => api.get('/users').then(res => res.data),
  });
}

// Mutation with cache invalidation
export function useCreateUser() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: (data: CreateUserInput) => api.post('/users', data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });
}

API Client (Axios) (CRITICAL)

import axios from 'axios';

export const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || '/api',
  timeout: 10000,
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      useAuthStore.getState().logout();
    }
    return Promise.reject(error);
  }
);

Form Handling (HIGH)

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
});

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
  });

  const onSubmit = (data) => {
    // Handle login
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input {...register('email')} />
      {errors.email && <span>{errors.email.message}</span>}
      {/* ... */}
    </form>
  );
}

Component Pattern (MEDIUM)

// Use shadcn/ui + Tailwind
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';

interface CardProps {
  title: string;
  children: React.ReactNode;
  className?: string;
}

export function Card({ title, children, className }: CardProps) {
  return (
    <div className={cn('rounded-lg border p-4 shadow-sm', className)}>
      <h3 className="font-semibold text-lg mb-2">{title}</h3>
      {children}
    </div>
  );
}

Best Practices

  • Use TypeScript with strict mode
  • Prefer const over let
  • Use named exports for components
  • Keep components small and focused
  • Extract reusable logic into hooks
  • Use CSS-in-JS sparingly (prefer Tailwind)

Testing (HIGH)

  • Unit tests with Vitest + Testing Library
  • E2E tests with Playwright
  • Test user interactions, not implementation details

react-native-mobile

Version: 1.0.0

React Native Mobile Development

Build cross-platform mobile apps with React Native.

Impact Levels

LevelDescription
CRITICALMust follow - security, performance
HIGHShould follow - maintainability, UX
MEDIUMRecommended - code quality

Framework Selection (HIGH)

Use CaseFramework
Most apps (95%), MVPs, quick iterationExpo (managed)
Custom native modules, brownfield appsReact Native CLI

Tech Stack

  • UI: NativeWind (Tailwind CSS)
  • Navigation: React Navigation / Expo Router
  • State: Zustand + TanStack Query
  • Storage: MMKV / expo-secure-store
  • Forms: react-hook-form + Zod
  • Animations: react-native-reanimated

Critical Rules

1. NO INLINE STYLES (CRITICAL)

// FORBIDDEN
<View style={{flex: 1, padding: 16}}>
<View style={[styles.container, {marginTop: 10}]}>

// CORRECT - Use NativeWind
<View className="flex-1 p-4">
<View className="mt-2.5">

2. Use Shared Components (CRITICAL)

// CORRECT
import { Text } from '@/components/ui/Text';
import { Button } from '@/components/ui/Button';

// WRONG - Don't import Text from react-native
import { Text } from 'react-native';

Project Structure (Expo Router)

app/
├── (tabs)/              # Tab navigator
│   ├── _layout.tsx
│   ├── index.tsx        # Home
│   └── profile.tsx
├── (auth)/              # Auth stack
│   ├── _layout.tsx
│   ├── login.tsx
│   └── register.tsx
├── _layout.tsx          # Root layout
└── +not-found.tsx

components/
├── ui/                  # Shared UI
└── features/            # Feature-specific

stores/                  # Zustand stores
lib/                     # API, utils

Patterns

Navigation (Expo Router) (HIGH)

// app/_layout.tsx
import { Stack } from 'expo-router';

export default function RootLayout() {
  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Screen name="(tabs)" />
      <Stack.Screen name="(auth)" />
    </Stack>
  );
}

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Home, User } from 'lucide-react-native';

export default function TabLayout() {
  return (
    <Tabs>
      <Tabs.Screen 
        name="index" 
        options={{ 
          title: 'Home',
          tabBarIcon: ({ color }) => <Home color={color} />
        }} 
      />
    </Tabs>
  );
}

State Management (HIGH)

// stores/auth-store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

interface AuthState {
  token: string | null;
  user: User | null;
  setAuth: (token: string, user: User) => void;
  logout: () => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      token: null,
      user: null,
      setAuth: (token, user) => set({ token, user }),
      logout: () => set({ token: null, user: null }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

API Client

// lib/api.ts
import axios from 'axios';
import Constants from 'expo-constants';
import { useAuthStore } from '@/stores/auth-store';

const API_URL = Constants.expoConfig?.extra?.apiUrl;

export const api = axios.create({
  baseURL: API_URL,
  timeout: 10000,
});

api.interceptors.request.use((config) => {
  const token = useAuthStore.getState().token;
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});

Secure Storage (CRITICAL)

import * as SecureStore from 'expo-secure-store';

export const secureStorage = {
  async get(key: string): Promise<string | null> {
    return SecureStore.getItemAsync(key);
  },
  async set(key: string, value: string): Promise<void> {
    await SecureStore.setItemAsync(key, value);
  },
  async remove(key: string): Promise<void> {
    await SecureStore.deleteItemAsync(key);
  },
};

UI Components

// components/ui/Button.tsx
import { TouchableOpacity, Text, View } from 'react-native';
import { cn } from '@/lib/utils';

interface ButtonProps {
  onPress: () => void;
  variant?: 'primary' | 'secondary';
  children: React.ReactNode;
  className?: string;
}

export function Button({ 
  onPress, 
  variant = 'primary', 
  children,
  className 
}: ButtonProps) {
  return (
    <TouchableOpacity
      onPress={onPress}
      className={cn(
        'py-3 px-6 rounded-lg items-center',
        variant === 'primary' && 'bg-primary',
        variant === 'secondary' && 'border border-primary bg-transparent',
        className
      )}
    >
      {children}
    </TouchableOpacity>
  );
}

Performance (HIGH)

  • Use FlatList for long lists (not ScrollView)
  • Use useMemo for expensive calculations
  • Use expo-image or FastImage for images
  • Use react-native-reanimated for 60fps animations
  • Avoid re-renders with memo() for heavy components

EAS Build & Submit

# Install
npm install -g eas-cli

# Build
eas build --platform ios --profile production
eas build --platform android --profile production

# Submit
eas submit --platform ios
eas submit --platform android

Testing

  • Jest + React Native Testing Library
  • Detox for E2E
  • Test user flows, not implementation

tdd

Version: 1.0.0

Test-Driven Development

Write tests before code using the RED-GREEN-REFACTOR cycle.

Impact Levels

LevelDescription
CRITICALMust follow - test coverage
HIGHShould follow - test quality
MEDIUMRecommended - patterns

The TDD Cycle (CRITICAL)

┌─────────────────────────────────────────┐
│                                         │
│   RED → GREEN → REFACTOR → (repeat)     │
│                                         │
│   Fail   Pass    Improve                │
│                                         │
└─────────────────────────────────────────┘

RED: Write a Failing Test

Write a test for the behavior you want. The test should fail because the code doesn't exist yet.

// Step 1: Write the test FIRST
describe('UserService', () => {
  it('should create a user with valid data', async () => {
    const service = new UserService(mockRepo);
    
    const user = await service.createUser({
      email: 'test@example.com',
      name: 'Test User',
    });
    
    expect(user.id).toBeDefined();
    expect(user.email).toBe('test@example.com');
  });
});

GREEN: Make It Pass

Write the minimal code to make the test pass. Don't over-engineer.

// Step 2: Write MINIMAL code to pass
class UserService {
  constructor(private repo: UserRepository) {}
  
  async createUser(data: CreateUserInput): Promise<User> {
    return this.repo.create({
      id: generateId(),
      email: data.email,
      name: data.name,
      createdAt: new Date(),
    });
  }
}

REFACTOR: Improve the Code

Clean up while keeping tests green. Extract functions, rename variables, remove duplication.

// Step 3: Improve while staying GREEN
class UserService {
  constructor(private repo: UserRepository) {}
  
  async createUser(data: CreateUserInput): Promise<User> {
    this.validateEmail(data.email);
    return this.repo.create(this.buildUser(data));
  }
  
  private validateEmail(email: string): void {
    if (!isValidEmail(email)) {
      throw new AppError('Invalid email', 422);
    }
  }
  
  private buildUser(data: CreateUserInput): User {
    return {
      id: generateId(),
      email: data.email,
      name: data.name,
      createdAt: new Date(),
    };
  }
}

Test Structure (HIGH)

AAA Pattern (Arrange-Act-Assert)

it('should calculate order total with discount', () => {
  // Arrange - Set up test data
  const order = {
    items: [
      { price: 100, quantity: 2 },
      { price: 50, quantity: 1 },
    ],
    discountPercent: 10,
  };
  
  // Act - Execute the code under test
  const total = calculateOrderTotal(order);
  
  // Assert - Verify the result
  expect(total).toBe(225); // (200 + 50) * 0.9
});

Test Naming

// Pattern: should [expected behavior] when [condition]

it('should return user when found', async () => {});
it('should throw NotFoundError when user does not exist', async () => {});
it('should apply discount when coupon is valid', async () => {});

What to Test (HIGH)

Happy Path

it('should create user successfully with valid data', async () => {
  const user = await service.createUser(validData);
  expect(user).toBeDefined();
});

Error Cases

it('should throw ValidationError for invalid email', async () => {
  await expect(service.createUser({ email: 'invalid' }))
    .rejects.toThrow('Invalid email');
});

Edge Cases

it('should handle empty array gracefully', async () => {
  const result = await service.processItems([]);
  expect(result).toEqual([]);
});

it('should handle null input', async () => {
  const result = service.formatName(null);
  expect(result).toBe('');
});

Boundary Conditions

it('should reject password shorter than 8 characters', () => {
  expect(() => validatePassword('1234567')).toThrow();
});

it('should accept password of exactly 8 characters', () => {
  expect(() => validatePassword('12345678')).not.toThrow();
});

Mocking (MEDIUM)

Mock Dependencies

describe('UserService', () => {
  let service: UserService;
  let mockRepo: jest.Mocked<UserRepository>;
  
  beforeEach(() => {
    mockRepo = {
      create: jest.fn(),
      findById: jest.fn(),
      update: jest.fn(),
      delete: jest.fn(),
    };
    service = new UserService(mockRepo);
  });
  
  it('should call repository with correct data', async () => {
    mockRepo.create.mockResolvedValue({ id: '1', ...validData });
    
    await service.createUser(validData);
    
    expect(mockRepo.create).toHaveBeenCalledWith(
      expect.objectContaining({ email: validData.email })
    );
  });
});

Mock External Services

jest.mock('@/lib/email', () => ({
  sendEmail: jest.fn().mockResolvedValue({ success: true }),
}));

it('should send welcome email after registration', async () => {
  await service.registerUser(userData);
  
  expect(sendEmail).toHaveBeenCalledWith({
    to: userData.email,
    template: 'welcome',
  });
});

Coverage Requirements (CRITICAL)

TierCoverage
Starter50%
Standard80%
Strict95%

Anti-Patterns to Avoid (HIGH)

// DON'T: Test implementation details
it('should call private method', () => {
  // Testing internal implementation, not behavior
});

// DO: Test behavior/outcomes
it('should return formatted user name', () => {
  expect(formatUser(user)).toEqual(expectedOutput);
});

// DON'T: Multiple assertions testing different things
it('should work correctly', () => {
  expect(result.name).toBe('test');
  expect(result.email).toBe('test@test.com');
  expect(result.age).toBe(25);
  expect(service.callCount).toBe(1); // Different concern!
});

// DO: One concept per test
it('should return user with correct name', () => {});
it('should return user with correct email', () => {});

Impact Levels Reference

LevelDescriptionAction
CRITICALMust followBlocks merge if violated
HIGHShould followFix before merge
MAJORImportant issueAddress in this PR
MEDIUMRecommendedConsider fixing
MINORNice to haveOptional fix
NITStyle preferenceAuthor's choice

Generated on 2026-05-25 by compile-skills.js

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.