CtrlK
BlogDocsLog inGet started
Tessl Logo

deploy-docker

Skill do DevOps/Deployer para dockerização, CI/CD, e deploy de aplicações. Use quando precisar criar Dockerfile, docker-compose, configurar pipeline CI/CD, deploy em cloud, configurar nginx, SSL, ou qualquer operação de infraestrutura. Trigger em: "deploy", "Docker", "Dockerfile", "docker-compose", "CI/CD", "pipeline", "GitHub Actions", "nginx", "SSL", "produção", "staging", "Kubernetes", "AWS", "infraestrutura", "ambiente", "build", "release".

60

Quality

71%

Does it follow best practices?

Impact

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Optimize this skill with Tessl

npx tessl skill review --optimize ./skills/07-deploy-docker/SKILL.md
SKILL.md
Quality
Evals
Security

Deployer - Dockerização e Deploy

O Deployer é o último passo. Recebe código aprovado pelo security review e coloca em produção.

Governanca Global

Esta skill segue GLOBAL.md, policies/execution.md, policies/handoffs.md, policies/quality-gates.md, policies/token-efficiency.md, policies/stack-flexibility.md, policies/tool-safety.md e policies/evals.md.

Para exemplos completos de Dockerfile, compose, CI/CD e estrategias de release, consultar docs/skill-guides/deploy-docker.md apenas quando necessario.

Quando Usar

  • empacotar, publicar ou promover mudanca entre ambientes
  • configurar pipeline, health check, rollback ou release strategy

Quando Nao Usar

  • para validar codigo de negocio como atividade principal
  • para executar acao externa de alto risco sem aprovacao

Entradas Esperadas

  • artefatos aprovados por QA, Security e Reviewer
  • requisitos de ambiente, health check e rollback
  • restricoes operacionais e de observabilidade

Saidas Esperadas

  • estrategia de deploy clara
  • configuracao de build/release coerente
  • handoff operacional curto para execucao ou monitoramento

Responsabilidades

  1. Dockerizar a aplicação (front + back + banco)
  2. Configurar CI/CD pipeline
  3. Gerenciar ambientes (dev, staging, prod)
  4. Configurar reverse proxy (nginx/Traefik)
  5. SSL/TLS com certbot
  6. Monitoramento e logs
  7. Rollback strategy

Estrategia Base de Deploy

  • builds reproduziveis e multi-stage quando fizer sentido
  • imagens minimas e sem secrets embutidos
  • health checks, rollback e monitoramento antes de promover release
  • aprovacao explicita para acoes em producao seguindo policies/tool-safety.md

Para exemplos completos de Dockerfile, compose e pipelines, consultar docs/skill-guides/deploy-docker.md.

Dockerfile - Backend (Node.js/Express)

# Dockerfile.backend

FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production

FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npx prisma generate
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production

RUN addgroup --system --gid 1001 nodejs && \
    adduser --system --uid 1001 appuser

COPY --from=builder --chown=appuser:nodejs /app/dist ./dist
COPY --from=builder --chown=appuser:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:nodejs /app/prisma ./prisma
COPY --from=builder /app/package.json ./

USER appuser

EXPOSE 3001

HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1

CMD ["sh", "-c", "npx prisma migrate deploy && node dist/server.js"]

Docker Compose - Ambiente Completo

# docker-compose.yml
version: '3.8'

services:
  # ── Frontend ──
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.frontend
      args:
        NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
        NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
    restart: unless-stopped
    ports:
      - "3000:3000"
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - app-network
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'

  # ── Backend ──
  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile.backend
    restart: unless-stopped
    ports:
      - "3001:3001"
    env_file:
      - ./backend/.env
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: '0.5'

  # ── PostgreSQL ──
  postgres:
    image: postgres:16-alpine
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: ${DB_NAME}
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network
    deploy:
      resources:
        limits:
          memory: 256M

  # ── Redis ──
  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  # ── Nginx Reverse Proxy ──
  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - certbot_data:/etc/letsencrypt:ro
      - certbot_www:/var/www/certbot:ro
    depends_on:
      - frontend
      - backend
    networks:
      - app-network

  # ── Certbot SSL ──
  certbot:
    image: certbot/certbot
    volumes:
      - certbot_data:/etc/letsencrypt
      - certbot_www:/var/www/certbot
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"

volumes:
  postgres_data:
  redis_data:
  certbot_data:
  certbot_www:

networks:
  app-network:
    driver: bridge

Nginx Configuration

# nginx/conf.d/app.conf

upstream frontend {
    server frontend:3000;
}

upstream backend {
    server backend:3001;
}

# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;

server {
    listen 80;
    server_name seudominio.com;
    
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl http2;
    server_name seudominio.com;
    
    ssl_certificate /etc/letsencrypt/live/seudominio.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/seudominio.com/privkey.pem;
    
    # SSL hardening
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    
    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    
    # Gzip
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml;
    gzip_min_length 1000;
    
    # Frontend
    location / {
        proxy_pass http://frontend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
    
    # Backend API
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
    
    # Login rate limit mais agressivo
    location /api/v1/auth/login {
        limit_req zone=login burst=3 nodelay;
        
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    
    # Bloqueia acesso direto ao Prisma Studio, etc
    location ~ /_(next|prisma) {
        deny all;
    }
}

CI/CD - GitHub Actions

# .github/workflows/deploy.yml
name: CI/CD Pipeline

on:
  push:
    branches: [main, staging]
  pull_request:
    branches: [main]

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  # ── Lint & Type Check ──
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check

  # ── Unit Tests ──
  test-unit:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run test:coverage
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  # ── E2E Tests ──
  test-e2e:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npm run test:e2e
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

  # ── Security Audit ──
  security:
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm audit --audit-level=high
      - uses: aquasecurity/trivy-action@master
        with:
          scan-type: 'fs'
          severity: 'CRITICAL,HIGH'

  # ── Build & Push Docker ──
  build:
    needs: [test-unit, test-e2e, security]
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max

  # ── Deploy ──
  deploy-staging:
    needs: build
    if: github.ref == 'refs/heads/staging'
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - name: Deploy to staging
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.STAGING_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /app
            docker compose pull
            docker compose up -d --force-recreate
            docker compose exec backend npx prisma migrate deploy
            docker system prune -f

  deploy-production:
    needs: build
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment: production
    steps:
      - name: Deploy to production
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.PROD_HOST }}
          username: ${{ secrets.DEPLOY_USER }}
          key: ${{ secrets.DEPLOY_KEY }}
          script: |
            cd /app
            # Backup banco antes de deploy
            docker compose exec postgres pg_dump -U $DB_USER $DB_NAME > backup_$(date +%Y%m%d_%H%M).sql
            docker compose pull
            docker compose up -d --force-recreate
            docker compose exec backend npx prisma migrate deploy
            # Health check
            sleep 10
            curl -f http://localhost:3000/api/health || (docker compose logs --tail=50 && exit 1)
            docker system prune -f

Environment Variables

# .env.example — NUNCA commitar .env real
# === Backend ===
NODE_ENV=production
PORT=3001
DATABASE_URL=postgresql://user:pass@postgres:5432/dbname
REDIS_URL=redis://:pass@redis:6379

# Auth
JWT_ACCESS_SECRET=<gerar-com-openssl-rand-base64-64>
JWT_REFRESH_SECRET=<gerar-com-openssl-rand-base64-64>
JWT_ACCESS_EXPIRY=15m
JWT_REFRESH_EXPIRY=7d

# CORS
ALLOWED_ORIGINS=https://seudominio.com

# === Frontend ===
NEXT_PUBLIC_API_URL=https://seudominio.com/api/v1
NEXT_PUBLIC_APP_URL=https://seudominio.com

# === Infra ===
DB_NAME=appdb
DB_USER=appuser
DB_PASSWORD=<gerar-senha-forte>
REDIS_PASSWORD=<gerar-senha-forte>

Rollback Strategy

#!/bin/bash
# scripts/rollback.sh

PREVIOUS_TAG=$1

if [ -z "$PREVIOUS_TAG" ]; then
  echo "Uso: ./rollback.sh <tag-anterior>"
  echo "Tags disponíveis:"
  docker images --format "{{.Tag}}" ghcr.io/org/app | head -10
  exit 1
fi

echo "🔙 Rollback para $PREVIOUS_TAG..."

# Atualiza docker-compose pra usar tag anterior
export IMAGE_TAG=$PREVIOUS_TAG
docker compose up -d --force-recreate

# Verifica saúde
sleep 10
if curl -sf http://localhost:3000/api/health > /dev/null; then
  echo "✅ Rollback sucesso!"
else
  echo "❌ Rollback falhou — verificar logs"
  docker compose logs --tail=50
  exit 1
fi

Rollback Persistente — .last-tag pattern

Problema

O script de rollback acima exige que o operador saiba manualmente qual tag usar (./rollback.sh <tag-anterior>). Em pipelines CI/CD com múltiplos deploys por dia, a tag anterior não é conhecida em tempo de execução sem consultar registries externos — o que aumenta MTTR e risco de erro humano.

Solução: persistir tag em arquivo .last-tag

Antes de promover a nova tag, salve a tag atual em .last-tag no servidor. Em caso de rollback, leia o arquivo em vez de hardcodar.

#!/bin/bash
# scripts/deploy-with-tag-persist.sh
# Uso: ./deploy-with-tag-persist.sh ghcr.io/org/app:sha-abc123

REGISTRY=ghcr.io/org/app
LAST_TAG_FILE=/app/.last-tag
NEW_TAG=$1

if [ -z "$NEW_TAG" ]; then
  echo "Uso: $0 <nova-tag>"
  exit 1
fi

# Persiste tag atual como "last" antes de promover
if [ -f "$LAST_TAG_FILE" ]; then
  CURRENT_TAG=$(cat "$LAST_TAG_FILE")
  echo "Tag atual: $CURRENT_TAG → será salva como last"
fi
echo "$NEW_TAG" > "$LAST_TAG_FILE"

# Promove nova tag
export IMAGE_TAG=$NEW_TAG
docker compose up -d --force-recreate

sleep 10
if curl -sf http://localhost:3000/api/health > /dev/null; then
  echo "Deploy OK: $NEW_TAG"
else
  echo "Deploy falhou — iniciando rollback automático para $CURRENT_TAG"
  export IMAGE_TAG=$CURRENT_TAG
  echo "$CURRENT_TAG" > "$LAST_TAG_FILE"
  docker compose up -d --force-recreate
  exit 1
fi

# Rollback manual independente:
# docker pull $(cat /app/.last-tag)
# IMAGE_TAG=$(cat /app/.last-tag) docker compose up -d --force-recreate

Pattern em GitHub Actions

# No job deploy-production — persistir tag via SSH após health check
- name: Persist last-tag e deploy
  uses: appleboy/ssh-action@v1
  with:
    host: ${{ secrets.PROD_HOST }}
    username: ${{ secrets.DEPLOY_USER }}
    key: ${{ secrets.DEPLOY_KEY }}
    script: |
      cd /app
      NEW_TAG="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}"
      LAST_TAG_FILE=".last-tag"

      # Guarda tag anterior antes de promover
      [ -f "$LAST_TAG_FILE" ] && cp "$LAST_TAG_FILE" ".prev-tag"

      echo "$NEW_TAG" > "$LAST_TAG_FILE"
      IMAGE_TAG=$NEW_TAG docker compose up -d --force-recreate
      sleep 10
      curl -f http://localhost:3000/api/health || {
        echo "Health check falhou — rollback para $(cat .prev-tag)"
        IMAGE_TAG=$(cat .prev-tag) docker compose up -d --force-recreate
        cp .prev-tag "$LAST_TAG_FILE"
        exit 1
      }
      docker system prune -f

Pattern em docker-compose com variável de ambiente

# docker-compose.yml — ler tag de variável
services:
  backend:
    image: ${IMAGE_TAG:-ghcr.io/org/app:latest}
# rollback.sh sem argumento — usa .last-tag automaticamente
#!/bin/bash
ROLLBACK_TAG=$(cat /app/.last-tag 2>/dev/null)
if [ -z "$ROLLBACK_TAG" ]; then
  echo "Arquivo .last-tag não encontrado. Rollback manual necessário."
  exit 1
fi
echo "Rollback para: $ROLLBACK_TAG"
IMAGE_TAG=$ROLLBACK_TAG docker compose up -d --force-recreate

ssl-init.sh — Idempotent SSL Setup

Problema

O bloco SSL no nginx/conf.d/app.conf referencia fullchain.pem e privkey.pem que só existem após certbot certonly ter rodado com sucesso. Se o nginx subir antes do certbot, ele falha e o compose inteiro fica unhealthy — especialmente em primeiro deploy ou após troca de servidor.

Solução: script ssl-init.sh idempotente

#!/bin/bash
# scripts/ssl-init.sh
# Detecta se certificado existe e cria apenas se necessário.
# Idempotente: rodar múltiplas vezes é seguro.

set -euo pipefail

DOMAIN=${1:-"seudominio.com"}
EMAIL=${2:-"admin@seudominio.com"}
CERT_PATH="/etc/letsencrypt/live/$DOMAIN/fullchain.pem"

echo "[ssl-init] Verificando certificado para $DOMAIN..."

if [ -f "$CERT_PATH" ]; then
  # Verifica validade — renova se expira em menos de 30 dias
  EXPIRY=$(openssl x509 -enddate -noout -in "$CERT_PATH" | cut -d= -f2)
  EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -jf "%b %d %H:%M:%S %Y %Z" "$EXPIRY" +%s)
  NOW_EPOCH=$(date +%s)
  DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))

  if [ "$DAYS_LEFT" -gt 30 ]; then
    echo "[ssl-init] Certificado válido por mais $DAYS_LEFT dias. Nenhuma ação."
    exit 0
  fi
  echo "[ssl-init] Certificado expira em $DAYS_LEFT dias — renovando..."
fi

echo "[ssl-init] Obtendo certificado via certbot..."
certbot certonly \
  --webroot \
  --webroot-path /var/www/certbot \
  --email "$EMAIL" \
  --agree-tos \
  --no-eff-email \
  --non-interactive \
  -d "$DOMAIN" \
  -d "www.$DOMAIN" \
  2>&1 | tee -a /var/log/ssl-init.log

echo "[ssl-init] Recarregando nginx..."
docker compose exec nginx nginx -s reload 2>/dev/null || nginx -s reload

echo "[ssl-init] Certificado configurado com sucesso."

Como integrar no entrypoint do container

Opção 1 — rodar antes de subir nginx (recomendado para primeiro deploy):

# No script de deploy ou no docker-compose entrypoint do serviço nginx
entrypoint: >
  /bin/sh -c "
    /scripts/ssl-init.sh seudominio.com admin@seudominio.com &&
    nginx -g 'daemon off;'
  "

Opção 2 — nginx sobe com config HTTP-only primeiro, ssl-init promove para HTTPS:

# docker-compose.yml — nginx com dois configs
services:
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/conf.d/http-only.conf:/etc/nginx/conf.d/default.conf:ro  # inicial
      - ./nginx/conf.d/app.conf:/etc/nginx/conf.d/app.conf:ro            # após ssl-init
      - certbot_data:/etc/letsencrypt
      - certbot_www:/var/www/certbot
    command: >
      /bin/sh -c "
        nginx -g 'daemon off;' &
        sleep 5 &&
        /scripts/ssl-init.sh seudominio.com admin@seudominio.com &&
        cp /etc/nginx/conf.d/app.conf /etc/nginx/conf.d/default.conf &&
        nginx -s reload &&
        wait
      "

Cron para renovação automática no servidor:

# /etc/cron.d/ssl-renew
0 3 * * * root /app/scripts/ssl-init.sh seudominio.com admin@seudominio.com >> /var/log/ssl-renew.log 2>&1

Blue-Green Deployment

  • Manter dois ambientes identicos (blue e green)
  • Deploy no ambiente inativo
  • Health check no ambiente novo
  • Switch de trafego (nginx upstream swap)
  • Rollback instantaneo: reverter switch

Canary Release

  • Rotear 5-10% do trafego pro deploy novo
  • Monitorar metricas (error rate, latency, CPU)
  • Se ok apos 15min: aumentar pra 50% → 100%
  • Se anomalia: rollback imediato pra 0%

Feature Flags

export function isFeatureEnabled(feature: string): boolean {
  const flags = JSON.parse(process.env.FEATURE_FLAGS || '{}');
  return flags[feature] === true;
}
  • Usar pra deploy gradual de features arriscadas
  • Desligar feature sem redeploy

Checklist Pre-Deploy

☐ Todos os testes passando (unit + E2E)
☐ Security review aprovado
☐ npm audit sem HIGH/CRITICAL
☐ Docker build sem warnings
☐ .env configurado no servidor
☐ Migrations testadas em staging
☐ Backup do banco feito
☐ DNS apontando corretamente
☐ SSL certificado válido
☐ Rollback script testado
☐ Monitoring/alertas configurados
☐ README atualizado

Codigo Limpo

Codigo deve priorizar clareza. Comentarios so fazem sentido quando explicam contexto nao obvio, restricoes externas ou workarounds temporarios.

Integração com Pipeline

  • Orquestrador (skill 09): Coordena quando esta skill é invocada e define a próxima etapa
  • Context Manager (skill 08): Rastreia progresso das tasks dentro desta skill
  • Documentador (skill 10): Documenta entregas desta skill durante o desenvolvimento

Evidencia de Conclusao

  • estrategia de deploy, health check e rollback definidos
  • risco operacional e pre-condicoes destacados
  • aprovacoes necessarias explicitadas para producao

Handoff

Seguir policies/handoffs.md e, quando util, templates/handoff.md.

Repository
felvieira/claude-skills-fv
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.