# Feature Development Cookbook

> Complete workflow for building features using rdev composable monorepo templates with chassis patterns, design system, and auth integration.

## Overview

This cookbook documents the end-to-end workflow for developing features in a composable monorepo project. There are two approaches:

1. **SDLC-Driven** (Recommended) - Structured workflow using the SDLC system and Claude Code
2. **Manual** - Traditional development with manual planning and implementation

The SDLC-driven approach uses skeleton commands that Claude executes inside the project pod, producing structured artifacts (spec, design, tasks) that can be approved and tracked.

```
SDLC Feature Development Workflow
──────────────────────────────────
1. Create Feature    → POST /sdlc/features
2. Generate Spec     → Claude runs /spec-feature
3. Approve Spec      → POST /sdlc/features/{slug}/artifacts/spec/approve
4. Generate Design   → Claude runs /design-feature
5. Approve Design    → POST /sdlc/features/{slug}/artifacts/design/approve
6. Break Down Tasks  → Claude runs /breakdown-feature
7. Approve Tasks     → POST /sdlc/features/{slug}/artifacts/tasks/approve
8. Implement Tasks   → Claude runs /implement-task for each
9. Review & Merge    → Claude runs /review-feature, /merge-feature
```

---

## Quick Start: API-Driven Feature Development

This section shows the minimal API calls to develop a feature using Claude Code and the SDLC system.

### Prerequisites

```bash
export RDEV_API_URL="https://rdev.masq-ops.orchard9.ai"
export RDEV_API_KEY="<your-api-key>"
```

### 1. Create Feature

```bash
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"slug": "add-hello", "title": "Add /hello endpoint"}'
```

### 2. Generate Spec with Claude

```bash
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "/spec-feature add-hello",
    "auto_commit": true,
    "auto_push": true,
    "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"
  }'
# Returns: {"data": {"task_id": "abc123", ...}}
```

### 3. Check Build Status

```bash
curl "$RDEV_API_URL/builds/abc123" \
  -H "X-API-Key: $RDEV_API_KEY"
# Wait for status: "completed"
```

### 4. Check Classifier for Next Action

```bash
curl "$RDEV_API_URL/projects/myapp/sdlc/next?feature=add-hello" \
  -H "X-API-Key: $RDEV_API_KEY"
# Returns: {"action": "AWAIT_APPROVAL", "artifact": "spec", ...}
```

### 5. Approve Spec

```bash
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/spec/approve" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

### 6. Continue Through Phases

Repeat the pattern: **Generate artifact** → **Check classifier** → **Approve** → **Next phase**

```bash
# Generate design
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "/design-feature add-hello", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'

# Approve design
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/design/approve" \
  -H "X-API-Key: $RDEV_API_KEY" -H "Content-Type: application/json" -d '{}'

# Generate tasks
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "/breakdown-feature add-hello", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'

# Approve tasks
curl -X POST "$RDEV_API_URL/projects/myapp/sdlc/features/add-hello/artifacts/tasks/approve" \
  -H "X-API-Key: $RDEV_API_KEY" -H "Content-Type: application/json" -d '{}'

# Implement first task
curl -X POST "$RDEV_API_URL/projects/myapp/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "/implement-task add-hello T1", "auto_commit": true, "auto_push": true, "git_clone_url": "https://git.threesix.ai/jordan/myapp.git"}'
```

### E2E Test Script

Run the full workflow automatically:

```bash
./cookbooks/scripts/feature-dev-test.sh run my-test-project
./cookbooks/scripts/feature-dev-test.sh status my-test-project
./cookbooks/scripts/feature-dev-test.sh teardown my-test-project
```

---

## Skeleton Commands Reference

These commands are installed in every project's `.claude/commands/` directory and can be invoked via the `/builds` endpoint.

| Command | Description | Artifacts |
|---------|-------------|-----------|
| `/spec-feature <slug>` | Create specification document | `.sdlc/features/<slug>/spec.md` |
| `/design-feature <slug>` | Create technical design | `.sdlc/features/<slug>/design.md` |
| `/breakdown-feature <slug>` | Break feature into tasks | `.sdlc/features/<slug>/tasks.md` |
| `/implement-task <slug> <task-id>` | Implement a specific task | Code changes |
| `/create-qa-plan <slug>` | Create QA test plan | `.sdlc/features/<slug>/qa-plan.md` |
| `/run-qa <slug>` | Execute QA tests | Test results |
| `/review-feature <slug>` | Code review all changes | Review report |
| `/audit-feature <slug>` | Audit feature for compliance | Audit report |
| `/fix-review-issues <slug>` | Fix issues from review | Code changes |
| `/remediate-audit <slug>` | Fix audit findings | Code changes |
| `/fix-qa-failures <slug>` | Fix failing QA tests | Code changes |
| `/merge-feature <slug>` | Merge feature to main | Merged branch |
| `/archive-feature <slug>` | Archive completed feature | Archived state |
| `/next <slug>` | Get classifier recommendation | Next action |
| `/deliver <slug>` | Run full delivery pipeline | All artifacts |

---

## SDLC API Reference

### Feature Management

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/projects/{id}/sdlc/state` | GET | Get SDLC state for project |
| `/projects/{id}/sdlc/features` | GET | List all features |
| `/projects/{id}/sdlc/features` | POST | Create new feature |
| `/projects/{id}/sdlc/features/{slug}` | GET | Get feature details |
| `/projects/{id}/sdlc/features/{slug}` | DELETE | Delete feature |

### Artifacts

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/projects/{id}/sdlc/features/{slug}/artifacts` | GET | List artifact status |
| `/projects/{id}/sdlc/features/{slug}/artifacts/{type}/approve` | POST | Approve artifact |

### Tasks

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/projects/{id}/sdlc/features/{slug}/tasks` | GET | List tasks |
| `/projects/{id}/sdlc/features/{slug}/tasks` | POST | Add task |

### Workflow Control

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/projects/{id}/sdlc/features/{slug}/block` | POST | Block feature |
| `/projects/{id}/sdlc/features/{slug}/unblock` | POST | Unblock feature |
| `/projects/{id}/sdlc/next` | GET | Get classifier recommendation |

### Queries

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/projects/{id}/sdlc/query/blocked` | GET | List blocked features |
| `/projects/{id}/sdlc/query/ready` | GET | List ready features |
| `/projects/{id}/sdlc/query/needs-approval` | GET | List features awaiting approval |

---

## Manual Development Workflow

For cases where you want direct control without the SDLC system, you can follow the traditional development workflow below.

```
Manual Feature Development Workflow
────────────────────────────────────
1. Planning       → Define requirements, break into tasks
2. API Dev        → Bind + Wrap patterns, OpenAPI annotations
3. Frontend Dev   → Auth hooks, design system, API client
4. Testing        → Handler tests, integration tests
5. Review         → /review-code, quality checks
6. Deployment     → Git push, Woodpecker CI, verify
```

This approach assumes you have:

- An rdev project with monorepo skeleton (created via `POST /project`)
- At least one API service component (`services/api`)
- A Next.js or React app component (`apps/dashboard`)

### Project Structure

Your composable monorepo should have:

```
my-project/
├── pkg/                         # Shared Go packages
│   ├── app/                     # Service bootstrapper
│   │   ├── app.go              # NewApp, Run, RegisterRoutes
│   │   ├── handler.go          # Wrap pattern
│   │   ├── bind.go             # Bind + BindAndValidate
│   │   └── health.go           # Health probes
│   ├── httperror/              # Typed HTTP errors
│   ├── httpresponse/           # JSON response envelope
│   ├── httpvalidation/         # go-playground/validator
│   └── auth/                   # JWT + API key validation
├── packages/                    # Shared TS packages
│   ├── ui/                     # Design system components
│   ├── layout/                 # DashboardShell, Sidebar
│   ├── auth/                   # AuthProvider, useAuth
│   ├── logger/                 # Structured logging
│   └── api-client/             # Generated TypeScript client
├── services/                    # Go backend services
│   └── api/
│       ├── cmd/server/main.go
│       └── internal/api/routes.go
└── apps/                        # Frontend applications
    └── dashboard/
        └── src/app/             # Next.js App Router
```

---

## Step 1: Feature Planning

### Create Feature Branch
```bash
git checkout -b feature/user-profile
```

### Define Requirements

Example: User Profile Feature
- **Backend:** GET/PUT /api/users/me endpoints
- **Frontend:** Profile page with edit form
- **Validation:** Email format, name required
- **Auth:** Require authenticated user

### Break Into Tasks

```markdown
## Tasks

- [ ] Add User domain model with validation
- [ ] Implement GET /api/users/me handler
- [ ] Implement PUT /api/users/me handler
- [ ] Add OpenAPI annotations
- [ ] Create profile page component
- [ ] Add profile edit form
- [ ] Connect to API with generated client
- [ ] Write handler tests
- [ ] Run quality checks
```

---

## Step 2: API Development

### 2.1 Add Domain Model

Create the domain model in `services/api/internal/domain/`:

```go
// services/api/internal/domain/user.go
package domain

import "time"

// User represents an authenticated user's profile.
type User struct {
    ID        string    `json:"id" db:"id"`
    Email     string    `json:"email" db:"email"`
    Name      string    `json:"name" db:"name"`
    AvatarURL string    `json:"avatar_url,omitempty" db:"avatar_url"`
    CreatedAt time.Time `json:"created_at" db:"created_at"`
    UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}
```

### 2.2 Create Handler with Wrap Pattern

The `Wrap` pattern converts error-returning handlers to `http.HandlerFunc`:

```go
// services/api/internal/api/handlers/user.go
package handlers

import (
    "net/http"

    "my-project/pkg/app"
    "my-project/pkg/auth"
    "my-project/pkg/httperror"
    "my-project/pkg/httpresponse"
    "my-project/services/api/internal/service"
)

type UserHandler struct {
    svc    *service.UserService
    logger *slog.Logger
}

func NewUserHandler(svc *service.UserService, logger *slog.Logger) *UserHandler {
    return &UserHandler{svc: svc, logger: logger}
}

// GetMe returns the authenticated user's profile.
// Uses app.Wrap to convert error-returning handler to http.HandlerFunc.
func (h *UserHandler) GetMe() http.HandlerFunc {
    return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
        // Get user from auth context
        user, ok := auth.GetUser(r.Context())
        if !ok {
            return httperror.Unauthorized("not authenticated")
        }

        // Fetch full profile
        profile, err := h.svc.GetByID(r.Context(), user.ID)
        if err != nil {
            return httperror.WrapError(err, "failed to get profile")
        }

        httpresponse.JSON(w, http.StatusOK, profile)
        return nil
    })
}

// UpdateMe updates the authenticated user's profile.
// Uses app.BindAndValidate for request parsing and validation.
func (h *UserHandler) UpdateMe() http.HandlerFunc {
    type request struct {
        Name      string `json:"name" validate:"required,min=1,max=100"`
        AvatarURL string `json:"avatar_url" validate:"omitempty,url"`
    }

    return app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
        // Get user from auth context
        user, ok := auth.GetUser(r.Context())
        if !ok {
            return httperror.Unauthorized("not authenticated")
        }

        // Bind and validate request body
        var req request
        if err := app.BindAndValidate(r, &req); err != nil {
            return err // Already an HTTPError with validation details
        }

        // Update profile
        profile, err := h.svc.Update(r.Context(), user.ID, req.Name, req.AvatarURL)
        if err != nil {
            return httperror.WrapError(err, "failed to update profile")
        }

        httpresponse.JSON(w, http.StatusOK, profile)
        return nil
    })
}
```

### 2.3 Register Routes

```go
// services/api/internal/api/routes.go
func (s *Server) RegisterRoutes() {
    userHandler := handlers.NewUserHandler(s.userService, s.logger)

    // Protected routes (require auth)
    s.router.Group(func(r chi.Router) {
        r.Use(auth.Middleware(s.authValidator))

        r.Get("/api/users/me", userHandler.GetMe())
        r.Put("/api/users/me", userHandler.UpdateMe())
    })
}
```

### 2.4 Add OpenAPI Annotations

```go
// services/api/cmd/server/main.go
func buildOpenAPISpec() *api.OpenAPISpec {
    spec := api.NewOpenAPISpec("My Project API", "1.0.0", "/api")

    // Security schemes
    spec.WithBearerSecurity("bearerAuth", "JWT Bearer token")
    spec.WithGlobalSecurity("bearerAuth")

    // User profile endpoints
    spec.Op("GET", "/users/me", "Get current user profile",
        api.OpResponses(
            api.OpResponse(200, "User profile", api.Ref("User")),
            api.OpStandardResponses()...,
        ),
    )

    spec.OpWithBody("PUT", "/users/me", "Update current user profile",
        api.Object(map[string]*api.Schema{
            "name":       api.String().Description("Display name"),
            "avatar_url": api.String().Format("uri").Description("Avatar URL"),
        }, "name"),
        api.OpResponses(
            api.OpResponse(200, "Updated user profile", api.Ref("User")),
            api.OpStandardResponses()...,
        ),
    )

    // Schema definitions
    spec.Schema("User", api.Object(map[string]*api.Schema{
        "id":         api.UUID(),
        "email":      api.Email(),
        "name":       api.String(),
        "avatar_url": api.String().Format("uri"),
        "created_at": api.DateTime(),
        "updated_at": api.DateTime(),
    }))

    return spec
}
```

---

## Step 3: Frontend Development

### 3.1 Generate TypeScript Client

After adding OpenAPI annotations, regenerate the client:

```bash
./scripts/generate-client.sh
```

This updates `packages/api-client/src/schema.d.ts` with typed endpoints.

### 3.2 Create Profile Page

```tsx
// apps/dashboard/src/app/(dashboard)/profile/page.tsx
import { ProfileForm } from '@/components/profile-form';
import { Card, CardContent, CardHeader, CardTitle } from '@my-project/ui';

export default function ProfilePage() {
    return (
        <div className="p-6">
            <Card>
                <CardHeader>
                    <CardTitle>Profile Settings</CardTitle>
                </CardHeader>
                <CardContent>
                    <ProfileForm />
                </CardContent>
            </Card>
        </div>
    );
}
```

### 3.3 Create Profile Form with Auth Hook

```tsx
// apps/dashboard/src/components/profile-form.tsx
'use client';

import { useState } from 'react';
import { useAuth } from '@my-project/auth';
import { Button, Input, Label } from '@my-project/ui';
import { updateProfile } from '@/actions/profile';

export function ProfileForm() {
    const { user, refreshUser } = useAuth();
    const [name, setName] = useState(user?.name ?? '');
    const [avatarUrl, setAvatarUrl] = useState(user?.avatar_url ?? '');
    const [isSubmitting, setIsSubmitting] = useState(false);
    const [error, setError] = useState<string | null>(null);

    if (!user) {
        return <div>Loading...</div>;
    }

    async function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
        setIsSubmitting(true);
        setError(null);

        try {
            const result = await updateProfile({ name, avatar_url: avatarUrl });
            if (result.success) {
                await refreshUser();
            } else {
                setError(result.error ?? 'Failed to update profile');
            }
        } catch (err) {
            setError('An unexpected error occurred');
        } finally {
            setIsSubmitting(false);
        }
    }

    return (
        <form onSubmit={handleSubmit} className="space-y-4">
            <div className="space-y-2">
                <Label htmlFor="email">Email</Label>
                <Input
                    id="email"
                    type="email"
                    value={user.email}
                    disabled
                    className="bg-surface-50"
                />
            </div>

            <div className="space-y-2">
                <Label htmlFor="name">Display Name</Label>
                <Input
                    id="name"
                    value={name}
                    onChange={(e) => setName(e.target.value)}
                    placeholder="Enter your name"
                    required
                />
            </div>

            <div className="space-y-2">
                <Label htmlFor="avatar">Avatar URL</Label>
                <Input
                    id="avatar"
                    type="url"
                    value={avatarUrl}
                    onChange={(e) => setAvatarUrl(e.target.value)}
                    placeholder="https://example.com/avatar.png"
                />
            </div>

            {error && (
                <div className="text-sm text-error">{error}</div>
            )}

            <Button type="submit" disabled={isSubmitting}>
                {isSubmitting ? 'Saving...' : 'Save Changes'}
            </Button>
        </form>
    );
}
```

### 3.4 Create Server Action

```typescript
// apps/dashboard/src/actions/profile.ts
'use server';

import { revalidatePath } from 'next/cache';
import { cookies } from 'next/headers';

interface UpdateProfileInput {
    name: string;
    avatar_url?: string;
}

interface UpdateProfileResult {
    success: boolean;
    error?: string;
}

export async function updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {
    const token = cookies().get('auth_token')?.value;

    if (!token) {
        return { success: false, error: 'Not authenticated' };
    }

    const response = await fetch(`${process.env.API_URL}/api/users/me`, {
        method: 'PUT',
        headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${token}`,
        },
        body: JSON.stringify(input),
    });

    if (!response.ok) {
        const error = await response.json();
        return {
            success: false,
            error: error.error?.message ?? 'Failed to update profile',
        };
    }

    revalidatePath('/profile');
    return { success: true };
}
```

### 3.5 Add Navigation Link

```tsx
// apps/dashboard/src/components/sidebar-nav.tsx
import { User, Settings, Home } from 'lucide-react';

const navItems = [
    { label: 'Dashboard', href: '/dashboard', icon: Home },
    { label: 'Profile', href: '/profile', icon: User },
    { label: 'Settings', href: '/settings', icon: Settings },
];
```

---

## Step 4: Testing

### 4.1 Write Handler Tests

```go
// services/api/internal/api/handlers/user_test.go
package handlers_test

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

    "my-project/pkg/auth"
    "my-project/services/api/internal/api/handlers"
    "my-project/services/api/internal/service"
)

func TestUserHandler_GetMe(t *testing.T) {
    svc := service.NewUserService(/* mock deps */)
    handler := handlers.NewUserHandler(svc, slog.Default())

    t.Run("returns user profile", func(t *testing.T) {
        // Create request with auth context
        req := httptest.NewRequest("GET", "/api/users/me", nil)
        req = req.WithContext(auth.SetUser(req.Context(), &auth.User{
            ID:    "user-123",
            Email: "test@example.com",
        }))

        rr := httptest.NewRecorder()
        handler.GetMe().ServeHTTP(rr, req)

        if rr.Code != http.StatusOK {
            t.Errorf("expected 200, got %d: %s", rr.Code, rr.Body.String())
        }

        var resp map[string]any
        json.Unmarshal(rr.Body.Bytes(), &resp)

        data := resp["data"].(map[string]any)
        if data["id"] != "user-123" {
            t.Errorf("expected user-123, got %v", data["id"])
        }
    })

    t.Run("returns 401 without auth", func(t *testing.T) {
        req := httptest.NewRequest("GET", "/api/users/me", nil)
        rr := httptest.NewRecorder()

        handler.GetMe().ServeHTTP(rr, req)

        if rr.Code != http.StatusUnauthorized {
            t.Errorf("expected 401, got %d", rr.Code)
        }
    })
}

func TestUserHandler_UpdateMe(t *testing.T) {
    svc := service.NewUserService(/* mock deps */)
    handler := handlers.NewUserHandler(svc, slog.Default())

    t.Run("updates profile", func(t *testing.T) {
        body := `{"name": "New Name", "avatar_url": "https://example.com/avatar.png"}`
        req := httptest.NewRequest("PUT", "/api/users/me", strings.NewReader(body))
        req.Header.Set("Content-Type", "application/json")
        req = req.WithContext(auth.SetUser(req.Context(), &auth.User{
            ID: "user-123",
        }))

        rr := httptest.NewRecorder()
        handler.UpdateMe().ServeHTTP(rr, req)

        if rr.Code != http.StatusOK {
            t.Errorf("expected 200, got %d: %s", rr.Code, rr.Body.String())
        }
    })

    t.Run("validates required name", func(t *testing.T) {
        body := `{"name": ""}`
        req := httptest.NewRequest("PUT", "/api/users/me", strings.NewReader(body))
        req.Header.Set("Content-Type", "application/json")
        req = req.WithContext(auth.SetUser(req.Context(), &auth.User{
            ID: "user-123",
        }))

        rr := httptest.NewRecorder()
        handler.UpdateMe().ServeHTTP(rr, req)

        if rr.Code != http.StatusBadRequest {
            t.Errorf("expected 400, got %d", rr.Code)
        }
    })
}
```

### 4.2 Run Tests

```bash
# Run all tests
./scripts/quality.sh

# Run specific service tests
go test ./services/api/...

# Run with coverage
go test -cover ./services/api/...
```

---

## Step 5: Review & Quality Checks

### 5.1 Run Code Review

Use the built-in review command:

```bash
# In Claude Code
/review-code
```

This checks for:
- Completeness and accuracy
- Tech debt indicators
- Maintainability issues
- Extensibility concerns
- DRY/CLEAN violations

### 5.2 Run Quality Checks

```bash
# Lint Go code
golangci-lint run ./...

# Lint frontend
cd apps/dashboard && npm run lint

# Type check
cd apps/dashboard && npm run typecheck
```

### 5.3 Verify OpenAPI Spec

```bash
# Start the service locally
cd services/api && make run

# Check the spec
curl http://localhost:8001/openapi.json | jq '.paths["/users/me"]'

# View docs
open http://localhost:8001/docs
```

---

## Step 6: Deployment

### 6.1 Commit Changes

```bash
# Stage changes
git add services/api/internal/api/handlers/user.go
git add services/api/internal/api/routes.go
git add apps/dashboard/src/app/\(dashboard\)/profile/
git add apps/dashboard/src/components/profile-form.tsx
git add apps/dashboard/src/actions/profile.ts

# Commit
git commit -m "feat: add user profile feature

- Add GET/PUT /api/users/me endpoints with Wrap pattern
- Add profile page with edit form
- Add server action for profile updates
- Add handler tests"
```

### 6.2 Push and Monitor CI

```bash
# Push feature branch
git push -u origin feature/user-profile

# Monitor pipeline
./cookbooks/scripts/feature-test.sh status my-project
```

### 6.3 Create Pull Request

```bash
gh pr create --title "feat: add user profile feature" --body "
## Summary
- Added user profile endpoints (GET/PUT /api/users/me)
- Added profile page with edit form
- Integrated with auth system

## Testing
- Handler tests added
- Manual testing complete

## Checklist
- [x] Handler tests pass
- [x] Quality checks pass
- [x] OpenAPI spec updated
"
```

### 6.4 Verify Deployment

After merge to main:

```bash
# Check deployment
curl https://my-project.threesix.ai/api/users/me \
  -H "Authorization: Bearer $TOKEN" | jq .

# Open the app
open https://my-project.threesix.ai/profile
```

---

## Pattern Quick Reference

### Wrap Pattern
```go
app.Wrap(func(w http.ResponseWriter, r *http.Request) error {
    // Return errors, they become proper HTTP responses
    if err := doThing(); err != nil {
        return httperror.WrapError(err, "failed to do thing")
    }
    httpresponse.JSON(w, http.StatusOK, result)
    return nil
})
```

### Bind Pattern
```go
var req RequestType
if err := app.BindAndValidate(r, &req); err != nil {
    return err // Validation errors become 400 with details
}
```

### HTTPError Sentinels
```go
httperror.BadRequest("invalid input")
httperror.NotFound("user not found")
httperror.Unauthorized("not authenticated")
httperror.Forbidden("access denied")
httperror.Conflict("already exists")
httperror.Internal("something went wrong")
httperror.Validation("validation failed")
```

### Auth Context
```go
user, ok := auth.GetUser(r.Context())
if !ok {
    return httperror.Unauthorized("not authenticated")
}
```

### React Auth Hook
```tsx
const { user, isLoading, login, logout } = useAuth();
```

### Design System Components
```tsx
import { Button, Card, Input, Label, Badge } from '@my-project/ui';
import { DashboardShell, Sidebar, Header } from '@my-project/layout';
```

---

## E2E Test Scripts

### Feature Development (Claude + SDLC)

Test the complete feature development flow with Claude Code:

```bash
# Run full SDLC feature development flow
./cookbooks/scripts/feature-dev-test.sh run test-feature

# Check status
./cookbooks/scripts/feature-dev-test.sh status test-feature

# Cleanup
./cookbooks/scripts/feature-dev-test.sh teardown test-feature
```

### Scaffold Validation

Test that project scaffolding creates correct patterns:

```bash
# Create project and verify patterns
./cookbooks/scripts/scaffold-test.sh run test-scaffold

# Verify specific patterns
./cookbooks/scripts/scaffold-test.sh verify-patterns test-scaffold

# Cleanup
./cookbooks/scripts/scaffold-test.sh teardown test-scaffold
```

---

## Manual Troubleshooting

### Handler returns 500 instead of proper error
Check that you're using `httperror.*` functions, not raw `errors.New()`.

### Validation errors not showing details
Ensure you're using `app.BindAndValidate()` which includes validation details.

### Auth middleware not applying
Verify route is inside the `r.Group()` with `auth.Middleware()`.

### Generated client out of sync
Run `./scripts/generate-client.sh` after OpenAPI changes.

### Frontend not seeing auth user
Check that `AuthProvider` wraps your app in `providers.tsx`.

---

## Chassis Framework

The `pkg/chassis` package provides a convenience facade over `pkg/app`:

```go
import "my-project/pkg/chassis"

svc := chassis.New("my-service", chassis.WithDefaultPort(8080))
```

It re-exports: `New`, `Wrap`, `WrapWithLogger`, `Bind`, `BindAndValidate`, `BindStrict`, `NewHealthHandler`, `PingChecker`, `HTTPChecker`.

## OpenAPI Documentation

Each service defines its spec in `internal/api/spec.go`:

```go
spec := openapi.NewOpenAPISpec("My API", "1.0.0").
    WithBearerSecurity("bearer", "JWT token")

spec.WithSchema("User", openapi.Object(map[string]openapi.Schema{
    "id":    openapi.UUID(),
    "email": openapi.Email(),
}, "id", "email"))

spec.AddPath("/api/v1/users/{id}", "get", map[string]any{
    "summary":    "Get user",
    "tags":       []string{"Users"},
    "parameters": []any{openapi.IDParam()},
    "responses": map[string]any{
        "200": openapi.OpResponse("Success", openapi.ResponseSchema(openapi.Ref("User"))),
        "404": openapi.OpResponse("Not found", openapi.ErrorResponseSchema()),
    },
})

application.EnableDocs(spec) // Mounts /docs (Scalar UI) and /openapi.json
```

## Design System Components

Available from `@project/ui`:

| Component | Usage |
|-----------|-------|
| Button | Primary actions, variants: default, destructive, outline, ghost |
| Card | Content containers with CardHeader, CardContent, CardFooter |
| Input, Label | Form fields |
| Badge | Status indicators, variants: success, warning, error, info |
| Dialog | Modal dialogs |
| Table | Data tables |
| Select | Dropdowns |
| Alert | Notification banners, variants: default, destructive, success, warning |
| Textarea | Multiline input |
| DropdownMenu | Context menus with items, checkboxes, radio groups |
| Sheet | Slide-in panels (side: top, right, bottom, left) |

All use CSS custom properties: `var(--background)`, `var(--accent)`, `var(--border)`, etc.

---

## SDLC Troubleshooting

### Build returns "completed" but no artifacts created

The skeleton command may have failed silently. Check the build output:

```bash
curl "$RDEV_API_URL/builds/<task-id>" -H "X-API-Key: $RDEV_API_KEY" | jq '.data.result'
```

Common causes:
- Feature doesn't exist (create it first via `/sdlc/features`)
- Previous artifacts missing (spec must exist before design)
- SDLC CLI not installed in pod

### Classifier returns AWAIT_ARTIFACT but artifact exists

The artifact may exist in the file system but not registered with SDLC:

```bash
# Register artifact manually
curl -X POST "$RDEV_API_URL/projects/$PROJECT/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "sdlc artifact create <slug> <type>", "auto_commit": true, "auto_push": true, "git_clone_url": "<url>"}'
```

### Task implementation doesn't commit changes

Check that `auto_commit` and `auto_push` are set to `true`, and `git_clone_url` is provided:

```bash
curl -X POST "$RDEV_API_URL/projects/$PROJECT/builds" \
  -H "X-API-Key: $RDEV_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "/implement-task <slug> <task-id>",
    "auto_commit": true,
    "auto_push": true,
    "git_clone_url": "https://git.threesix.ai/jordan/<project>.git"
  }'
```

### Build times out

Builds have a default timeout of 10 minutes. Complex implementations may need longer. Check the build status periodically:

```bash
# Poll status
while true; do
  status=$(curl -s "$RDEV_API_URL/builds/<task-id>" -H "X-API-Key: $RDEV_API_KEY" | jq -r '.data.status // .status')
  echo "Status: $status"
  [[ "$status" == "completed" || "$status" == "failed" ]] && break
  sleep 10
done
```

### Feature stuck in wrong phase

Check the classifier recommendation to understand what's blocking:

```bash
curl "$RDEV_API_URL/projects/$PROJECT/sdlc/next?feature=<slug>" \
  -H "X-API-Key: $RDEV_API_KEY" | jq '.'
```

If needed, you can manually advance the phase by approving the pending artifact.

---

## Related

- [Composable App Cookbook](./composable-app.md) - Creating projects with components
- [Landing Page Cookbook](./landing-page.md) - Simple single-component sites
- [Composable Monorepo Guide](../.claude/guides/services/composable-monorepo.md)
- [API Framework Guide](../.claude/guides/packages/api-framework.md)
- [SDLC Guide](../.claude/guides/services/sdlc.md) - Full SDLC orchestration documentation
