implementer-agent-skill
Complete TDD workflow for implementing business logic (use cases) and API endpoints that make tests pass. Covers Zod safeParse validation, async/await patterns, Next.js API routes, service orchestration, and Clean Architecture compliance.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: implementer-agent-skill
description: Complete TDD workflow for implementing business logic (use cases) and API endpoints that make tests pass. Covers Zod safeParse validation, async/await patterns, Next.js API routes, service orchestration, and Clean Architecture compliance.
---
# Implementer Agent Technical Skill
**Purpose**: Guide Implementer Agent through creating use cases and API endpoints that make failing tests pass, following strict TDD principles (Red → Green → Refactor).
**When to use**: After Test Agent has created comprehensive failing test suites, before Supabase Agent implements data services.
---
## 🎯 Core Mission
You work in the **GREEN phase of TDD**: comprehensive test suites already exist and FAIL appropriately. Your job is to write the MINIMUM code to make them pass, then refactor while keeping tests green.
**Critical principle**: Tests are IMMUTABLE. You NEVER modify tests to make implementation pass—you implement to satisfy existing tests.
---
## 📋 6-PHASE WORKFLOW
### PHASE 0: Pre-Implementation Research (MANDATORY)
**⚠️ DO NOT SKIP THIS PHASE**
Before implementing ANY use cases or API endpoints, complete this research to understand requirements and verify latest patterns.
#### Step 0.1: Read and Understand Requirements
```bash
# 1. Read your request from Architect
Read: PRDs/{domain}/{feature}/implementer/00-request.md
# 2. Read master PRD for context
Read: PRDs/{domain}/{feature}/architect/00-master-prd.md
# 3. Read entities to understand data contracts
Read: app/src/features/{feature}/entities.ts
# 4. Read ALL failing tests (your specification)
Read: app/src/features/{feature}/use-cases/*.test.ts
Read: app/src/app/api/{feature}/route.test.ts
```
**Extract from tests**:
- ✅ Expected function signatures
- ✅ Expected input/output types
- ✅ Validation requirements (what schemas to use)
- ✅ Authorization checks required
- ✅ Business rules to implement
- ✅ Error cases to handle
- ✅ Service methods expected to be called
- ✅ Mock service behaviors
- ✅ API authentication requirements
- ✅ API response formats and status codes
#### Step 0.2: Consult Context7 for Latest Patterns (MANDATORY)
**⚠️ CRITICAL**: Always verify latest patterns before implementing.
```typescript
// Query 1: Zod validation best practices
await context7.get_library_docs({
context7CompatibleLibraryID: "/colinhacks/zod",
topic: "safeParse refinements custom validation error handling flatten",
tokens: 3000
})
// Query 2: Async error handling patterns
await context7.get_library_docs({
context7CompatibleLibraryID: "/microsoft/typescript",
topic: "async await error handling promises try catch patterns",
tokens: 2500
})
// Query 3: Next.js API Route patterns
await context7.get_library_docs({
context7CompatibleLibraryID: "/vercel/next.js",
topic: "app router api routes NextRequest NextResponse error handling authentication",
tokens: 3000
})
```
**Reference files** (if Context7 unavailable):
- `references/zod-validation-patterns.md` - safeParse, error handling
- `references/use-case-patterns.md` - Business logic templates
- `references/api-endpoint-patterns.md` - Next.js route handlers
#### Step 0.3: Map Service Interfaces
From test mocks, extract the service interface you'll need to call:
```typescript
// Example from tests
interface TaskService {
create(data: TaskCreate): Promise<Task>
getById(id: string): Promise<Task | null>
list(query: TaskQuery): Promise<Task[]>
update(id: string, data: TaskUpdate): Promise<Task>
delete(id: string): Promise<void>
}
```
**CRITICAL**: You CALL these services, you DON'T IMPLEMENT them (Supabase Agent does that).
**Checkpoint**: Do NOT proceed to Phase 1 until you have:
- ✅ Read all requirement documents
- ✅ Read and understood ALL failing tests
- ✅ Consulted Context7 for latest patterns
- ✅ Mapped all service interfaces needed
---
### PHASE 1: Run Tests (RED Phase Verification)
**Purpose**: Confirm tests fail appropriately before implementing.
#### Step 1.1: Run All Use Case Tests
```bash
# From app/ directory
cd app
npm run test -- src/features/{feature}/use-cases/
```
**Expected output** (CORRECT Red Phase):
```
FAIL create{Entity}.test.ts
● Test suite failed to run
ReferenceError: create{Entity} is not defined
```
**INCORRECT Red Phase** (means tests are calling broken implementation):
```
FAIL create{Entity}.test.ts
✕ should create entity (2 ms)
Expected: { id: 'uuid', ... }
Received: undefined
```
If you see the second case, tests are NOT in proper Red phase—notify Architect.
#### Step 1.2: Run All API Route Tests
```bash
npm run test -- src/app/api/{feature}/route.test.ts
```
**Expected**: All tests FAIL with "route handler not defined" or similar.
**Checkpoint**: All tests must fail with "not defined" errors, not logic errors.
---
### PHASE 2: Implement Use Cases (GREEN Phase)
**Principle**: Write the MINIMUM code to make use case tests pass.
**Implementation order**: Implement use cases FIRST, API endpoints SECOND.
#### Step 2.1: Use Case File Structure
**Location**: `app/src/features/{feature}/use-cases/{action}.ts`
**Template pattern**:
```typescript
import { z } from 'zod'
import { EntityCreateSchema, type Entity, type EntityCreate } from '../entities'
import type { EntityService } from '../services/entity.service'
// Custom error types
export class ValidationError extends Error {
constructor(
message: string,
public details: z.ZodFormattedError<any>
) {
super(message)
this.name = 'ValidationError'
}
}
export class AuthorizationError extends Error {
constructor(message: string) {
super(message)
this.name = 'AuthorizationError'
}
}
/**
* Create a new entity with validation and authorization
*/
export async function createEntity(
input: unknown,
userId: string,
service: EntityService
): Promise<Entity> {
// 1. Validate input with safeParse (NEVER use .parse())
const result = EntityCreateSchema.safeParse(input)
if (!result.success) {
throw new ValidationError(
'Invalid input data',
result.error.flatten()
)
}
const validated = result.data
// 2. Apply business rules
if (validated.someField === 'prohibited-value') {
throw new ValidationError(
'Business rule violation: someField cannot be prohibited-value',
{ formErrors: ['Business rule violation'], fieldErrors: {} }
)
}
// 3. Authorization check
if (!validated.organizationId) {
throw new AuthorizationError('Organization ID required')
}
// TODO: Check if user belongs to organization
// This will be implemented when auth context is available
// 4. Orchestrate service call
try {
const entity = await service.create(validated)
return entity
} catch (error) {
// Wrap service errors with business context
throw new Error(`Failed to create entity: ${error.message}`)
}
}
```
#### Step 2.2: Critical Patterns
**ALWAYS use safeParse, NEVER parse**:
```typescript
// ❌ WRONG - Throws, hard to test
const validated = schema.parse(input)
// ✅ CORRECT - Returns discriminated union
const result = schema.safeParse(input)
if (!result.success) {
throw new ValidationError('Invalid input', result.error.flatten())
}
const validated = result.data
```
**Dependency injection for testability**:
```typescript
// ✅ Service passed as parameter (can be mocked in tests)
export async function createEntity(
input: unknown,
service: EntityService // Injected
): Promise<Entity> {
return await service.create(validated)
}
```
**Error handling with context**:
```typescript
// ✅ Wrap service errors with business context
try {
return await service.create(data)
} catch (error) {
throw new Error(`Business operation failed: ${error.message}`)
}
```
#### Step 2.3: Run Tests After Each Use Case
```bash
npm run test:watch -- src/features/{feature}/use-cases/
```
**Iterate**: Write minimal code → Run tests → Fix → Repeat until ALL use case tests pass.
#### Step 2.4: Use Case Checklist (for EACH use case)
- [ ] ✅ Function signature matches tests
- [ ] ✅ Uses safeParse for validation (not parse)
- [ ] ✅ Custom error types defined and thrown
- [ ] ✅ Business rules implemented
- [ ] ✅ Authorization checks implemented
- [ ] ✅ Service calls orchestrated (not implemented)
- [ ] ✅ Error handling with context
- [ ] ✅ All tests for this use case pass
- [ ] ✅ No tests were modified
**Reference**: See `references/use-case-patterns.md` for complete templates.
---
### PHASE 2.5: Implement CASL Ability (IF Authorization Required)
**When to include**: If tests include `defineAbilitiesFor()` tests, implement the ability builder.
**Location**: `app/src/features/{feature}/abilities/defineAbility.ts`
**Purpose**: Implement client-side authorization logic that determines what users can do.
#### Step 2.5.1: Implement defineAbilitiesFor()
**Template pattern**:
```typescript
import { AbilityBuilder, createMongoAbility } from '@casl/ability';
import type { AppAbility, DefineAbilityInput, Actions, Subjects } from '../entities';
/**
* Build CASL ability based on user role and permissions
*
* CRITICAL: This logic must MATCH the RLS policies in the database.
* CASL is for UX (hiding buttons), RLS is for security (actual enforcement).
*/
export function defineAbilitiesFor({
user,
workspace,
permissions,
}: DefineAbilityInput): AppAbility {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
// ===== OWNER BYPASS =====
// Owner has full access to everything in their workspace
if (user.id === workspace.owner_id) {
can('manage', 'all');
return build();
}
// ===== SUPER ADMIN BYPASS (with restrictions) =====
// Super Admin has elevated access but with certain restrictions
const orgId = workspace.parent_id || workspace.id;
const isSuperAdmin = user.superAdminOrgs?.includes(orgId);
if (isSuperAdmin) {
can('manage', 'all');
// Restrictions: Things Super Admin CANNOT do
cannot('delete', 'Organization');
cannot('remove', 'Permission', { role: 'owner' });
cannot('transfer', 'Ownership');
return build();
}
// ===== NORMAL USERS: Map permissions to abilities =====
permissions.forEach((perm) => {
const [resource, action] = perm.full_name.split('.');
const subject = mapResourceToSubject(resource);
// Add conditional permissions if they exist
if (perm.conditions) {
can(action as Actions, subject, perm.conditions);
} else {
can(action as Actions, subject);
}
});
return build();
}
/**
* Convert database resource names to CASL subjects
* Database uses snake_case, plural (boards)
* CASL uses PascalCase, singular (Board)
*/
function mapResourceToSubject(resource: string): Subjects {
const mapping: Record<string, Subjects> = {
'boards': 'Board',
'cards': 'Card',
'comments': 'Comment',
'custom_fields': 'CustomField',
'labels': 'Label',
};
return mapping[resource] || 'all';
}
```
#### Step 2.5.2: Implement loadUserAbility Use Case
**Location**: `app/src/features/{feature}/use-cases/loadUserAbility.ts`
**Purpose**: Server-side use case to load user's ability object.
```typescript
import { defineAbilitiesFor } from '../abilities/defineAbility';
import type { AppAbility } from '../entities';
import type { UserService } from '@/features/users/services/user.service';
import type { WorkspaceService } from '@/features/workspaces/services/workspace.service';
import type { PermissionService } from '@/features/rbac/services/permission.service';
export async function loadUserAbility(
userId: string,
workspaceId: string,
services: {
userService: UserService;
workspaceService: WorkspaceService;
permissionService: PermissionService;
}
): Promise<AppAbility> {
// 1. Load user data
const user = await services.userService.getById(userId);
if (!user) {
throw new Error('User not found');
}
// 2. Load workspace data
const workspace = await services.workspaceService.getById(workspaceId);
if (!workspace) {
throw new Error('Workspace not found');
}
// 3. Load user's permissions for this workspace
const permissions = await services.permissionService.getUserPermissions(
userId,
workspaceId
);
// 4. Build and return ability
return defineAbilitiesFor({
user,
workspace,
permissions,
});
}
```
#### Step 2.5.3: CASL Implementation Checklist
- [ ] ✅ defineAbilitiesFor() signature matches entities.ts
- [ ] ✅ Owner bypass implemented (can('manage', 'all'))
- [ ] ✅ Super Admin bypass with restrictions implemented
- [ ] ✅ Normal user permission mapping implemented
- [ ] ✅ Resource-to-subject mapping function created
- [ ] ✅ Conditional permissions handled (if applicable)
- [ ] ✅ Field-level permissions handled (if applicable)
- [ ] ✅ loadUserAbility() use case implemented
- [ ] ✅ All CASL ability tests pass
- [ ] ✅ No tests were modified
**Critical Rules**:
- ✅ CASL logic MUST match RLS policies (defense in depth)
- ✅ Owner always gets `can('manage', 'all')`
- ✅ Super Admin gets `can('manage', 'all')` with explicit `cannot()` restrictions
- ✅ Normal users map permissions via `permissions.forEach()`
- ❌ NEVER implement business logic in ability builder (pure authorization only)
- ❌ NEVER trust CASL alone for security (RLS is the actual enforcement)
**Coordination with Supabase Agent**:
- Your `defineAbilitiesFor()` logic defines WHAT users can do
- Supabase Agent's RLS policies ENFORCE the same rules at database level
- Architect will ensure both agents implement the SAME authorization logic
- If logic differs, it's a BUG that must be fixed
**Example of CASL + RLS Alignment**:
```typescript
// CASL (your code):
if (user.id === workspace.owner_id) {
can('delete', 'Board');
}
// RLS (Supabase Agent's code):
CREATE POLICY "Owner can delete boards"
ON boards FOR DELETE
USING (auth.uid() = (
SELECT owner_id FROM workspaces
WHERE id = boards.workspace_id
));
```
Both implement the same rule: only workspace owners can delete boards.
---
### PHASE 3: Implement API Endpoints (GREEN Phase)
**Principle**: API endpoints are THIN controllers that orchestrate use cases.
**Location**: `app/src/app/api/{feature}/route.ts` and `app/src/app/api/{feature}/[id]/route.ts`
#### Step 3.1: API Route Structure
**Template pattern**:
```typescript
import { NextRequest, NextResponse } from 'next/server'
import { createEntity } from '@/features/{feature}/use-cases/createEntity'
import { createClient } from '@/lib/supabase-server'
import { entityService } from '@/features/{feature}/services/entity.service'
export async function POST(request: NextRequest) {
try {
// 1. AUTHENTICATION (ALWAYS FIRST)
const supabase = createClient()
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
return NextResponse.json(
{
error: {
code: 'UNAUTHORIZED',
message: 'Authentication required'
}
},
{ status: 401 }
)
}
// 2. Parse request body
const body = await request.json()
// 3. Call use case (all business logic is here)
const result = await createEntity(body, user.id, entityService)
// 4. Return success response
return NextResponse.json(
{ data: result },
{ status: 201 }
)
} catch (error) {
// 5. Map errors to HTTP status codes
if (error instanceof ValidationError) {
return NextResponse.json(
{
error: {
code: 'VALIDATION_ERROR',
message: error.message,
details: error.details
}
},
{ status: 400 }
)
}
if (error instanceof AuthorizationError) {
return NextResponse.json(
{
error: {
code: 'FORBIDDEN',
message: error.message
}
},
{ status: 403 }
)
}
// Generic server error
console.error('API Error:', error)
return NextResponse.json(
{
error: {
code: 'INTERNAL_ERROR',
message: 'An unexpected error occurred'
}
},
{ status: 500 }
)
}
}
```
#### Step 3.2: Critical API Patterns
**Authentication ALWAYS first**:
```typescript
// ✅ CORRECT - Check auth before any processing
const { data: { user }, error } = await supabase.auth.getUser()
if (error || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
```
**Thin controllers - NO business logic**:
```typescript
// ❌ WRONG - Business logic in API endpoint
export async function POST(request: NextRequest) {
const body = await request.json()
if (body.name.length < 3) { // Business logic!
return NextResponse.json({ error: 'Name too short' }, { status: 400 })
}
return NextResponse.json({ data: result })
}
// ✅ CORRECT - Delegate to use case
export async function POST(request: NextRequest) {
const body = await request.json()
// Use case handles ALL business logic
const result = await createEntity(body, user.id, service)
return NextResponse.json({ data: result }, { status: 201 })
}
```
**Consistent error response format**:
```typescript
// ✅ Standard error format
{
error: {
code: 'ERROR_CODE',
message: 'Human-readable message',
details: {} // Optional, for validation errors
}
}
```
#### Step 3.3: Run API Tests
```bash
npm run test:watch -- src/app/api/{feature}/
```
**Iterate**: Implement endpoint → Run tests → Fix → Repeat until ALL API tests pass.
#### Step 3.4: API Endpoint Checklist (for EACH endpoint)
- [ ] ✅ Authentication check is FIRST
- [ ] ✅ Request body parsed and validated
- [ ] ✅ Use case called (no business logic in endpoint)
- [ ] ✅ Errors mapped to correct HTTP status codes
- [ ] ✅ Response format is consistent
- [ ] ✅ All tests for this endpoint pass
- [ ] ✅ No tests were modified
**Reference**: See `references/api-endpoint-patterns.md` for complete templates.
---
### PHASE 4: Refactor (Keep Tests Green)
**Principle**: Improve code quality WITHOUT changing behavior.
**Refactoring opportunities**:
1. **Extract validation helpers**:
```typescript
// Before
export async function createTask(...) {
const result = TaskCreateSchema.safeParse(input)
if (!result.success) {
throw new ValidationError('Invalid input', result.error.flatten())
}
const validated = result.data
// ...
}
// After (extracted)
function validateInput<T>(schema: z.ZodSchema<T>, input: unknown): T {
const result = schema.safeParse(input)
if (!result.success) {
throw new ValidationError('Invalid input', result.error.flatten())
}
return result.data
}
export async function createTask(...) {
const validated = validateInput(TaskCreateSchema, input)
// ...
}
```
2. **Extract authorization helpers**:
```typescript
function ensureUserInOrganization(userId: string, organizationId: string) {
// Check if user belongs to org
if (!hasAccess) {
throw new AuthorizationError('User not in organization')
}
}
```
3. **Simplify error handling**:
```typescript
// Centralized error wrapper
function wrapServiceError(operation: string, error: Error): never {
throw new Error(`${operation} failed: ${error.message}`)
}
try {
return await service.create(data)
} catch (error) {
wrapServiceError('Create entity', error)
}
```
**CRITICAL**: Run tests after EVERY refactoring change. Tests must stay green.
```bash
npm run test:watch
```
---
### PHASE 5: Validation & Coverage
**Purpose**: Verify implementation completeness and quality.
#### Step 5.1: Run Full Test Suite
```bash
# Run all tests
npm run test
# Expected: ALL tests PASS
# ✅ use-cases/*.test.ts - PASS (100%)
# ✅ api/route.test.ts - PASS (100%)
```
#### Step 5.2: Check Coverage
```bash
npm run test:coverage
```
**Required**: >90% coverage for all implemented use cases.
```
File | % Stmts | % Br