mozak.tech Engenharia de IA Corporativa 27%

Parte I — Operações e Infraestrutura Potencializadas por IA

1.8 — Pipelines de Entrega Contínua com Decisões Assistidas por IA (CI/CD + IA)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Gerar workflows GitHub Actions YAML via linguagem natural usando Claude

Implementar gateQualidade() com critérios: 0 falhas + cobertura mínima

Implementar gateCusto() com 3 faixas: bloquear / avisar / aprovar silenciosamente

Usar Claude para analisar PRs e sugerir reviewers e checklist

Completar os 2 TODOs: gateQualidade e gateCusto

Por que isso importa

CI/CD é o gargalo de velocidade de equipes de engenharia. Escrever workflows GitHub Actions corretamente exige memorizar sintaxe YAML específica, actions disponíveis e sua versão, ordem de steps. Um copilot que gera isso via NL acelera o bootstrap de novos projetos.

Mais crítico ainda: gates inteligentes previnem deploys problemáticos. Um gate de qualidade que bloqueia quando cobertura cai abaixo de 80% evita regressões silenciosas. Um gate de custo que bloqueia quando infraestrutura vai custar $130/mês a mais — sem aprovação explícita — previne surpresas na fatura da AWS.

Conceitos Fundamentais

Gerador de GitHub Actions

async function gerarWorkflow(descricao: string): Promise<string> {

  const response = await client.messages.create({

    model: 'claude-haiku-4-5-20251001',

    max_tokens: 1500,

    system: `Você é especialista em GitHub Actions. Gere um workflow YAML válido.

Sempre inclua: name, on (trigger), jobs com steps.

Use actions populares: actions/checkout@v4, actions/setup-node@v4, etc.

Retorne APENAS o YAML, sem explicação.`,

    messages: [{ role: 'user', content: `Crie um GitHub Actions workflow para: ${descricao}` }],

  })

  return response.content[0].type === 'text' ? response.content[0].text : ''

}

TODO 1: gateQualidade(resultado, cobertura_minima)

async function gateQualidade(resultado: ResultadoTestes, cobertura_minima: number = 80): Promise<string> {

  const problemas: string[] = []

  

  if (resultado.falhando > 0) {

    problemas.push(`${resultado.falhando} teste(s) falhando`)

  }

  

  if (resultado.cobertura < cobertura_minima) {

    problemas.push(`cobertura ${resultado.cobertura}% abaixo do mínimo de ${cobertura_minima}%`)

  }

  

  if (problemas.length > 0) {

    return `❌ GATE REPROVADO:\n${problemas.map(p => `  • ${p}`).join('\n')}\n\n` +

           `Resultado: ${resultado.passando}/${resultado.total} testes, ` +

           `${resultado.cobertura}% cobertura, ` +

           `${resultado.duracao_segundos.toFixed(1)}s`

  }

  

  return `✅ GATE APROVADO:\n` +

         `  • ${resultado.passando}/${resultado.total} testes passando\n` +

         `  • Cobertura: ${resultado.cobertura}% (mínimo: ${cobertura_minima}%)\n` +

         `  • Duração: ${resultado.duracao_segundos.toFixed(1)}s`

}

2 critérios mandatórios: zero falhas E cobertura acima do mínimo. Ambos precisam passar.

TODO 2: gateCusto(estimativa, limite_delta_usd)

async function gateCusto(estimativa: EstimativaCusto, limite_delta_usd: number = 100): Promise<string> {

  const { delta, custo_mensal_atual, custo_mensal_novo, recursos_adicionados, recursos_removidos } = estimativa

  

  const detalhes = [

    `  • Custo atual: $${custo_mensal_atual.toFixed(2)}/mês`,

    `  • Custo novo: $${custo_mensal_novo.toFixed(2)}/mês`,

    `  • Delta: +$${delta.toFixed(2)}/mês`,

    ...recursos_adicionados.map(r => `  + ${r}`),

    ...recursos_removidos.map(r => `  - ${r}`),

  ].join('\n')

  

  // Faixa 1: delta > limite → bloquear

  if (delta > limite_delta_usd) {

    return `❌ GATE DE CUSTO REPROVADO:\n${detalhes}\n\n` +

           `Delta $${delta.toFixed(2)} excede o limite de $${limite_delta_usd}.\n` +

           `Requer aprovação explícita de FinOps antes do deploy.`

  }

  

  // Faixa 2: delta entre 50%-100% do limite → aviso

  if (delta > limite_delta_usd * 0.5) {

    return `⚠️ AVISO DE CUSTO (aprovado com aviso):\n${detalhes}\n\n` +

           `Delta $${delta.toFixed(2)} está em ${Math.round(delta/limite_delta_usd*100)}% do limite.`

  }

  

  // Faixa 3: delta < 50% do limite → aprovar silenciosamente

  return `✅ GATE DE CUSTO APROVADO:\n${detalhes}`

}

3 faixas: bloquear (>100%), avisar (50-100%), aprovar (<50%). Lógica de thresholds em cascata — if > 100 → else if > 50 → else.

Aprofundamento Técnico

Integração com Infracost Real

import { execSync } from 'child_process'



function rodarInfracost(dirTerraform: string): EstimativaCusto {

  // Gerar baseline (versão atual)

  execSync(`infracost breakdown --path=${dirTerraform} --format=json > baseline.json`)

  

  // Gerar estimate para nova versão

  execSync(`infracost breakdown --path=${dirTerraform} --compare-to=baseline.json --format=json > nova.json`)

  

  const nova = JSON.parse(fs.readFileSync('nova.json', 'utf-8'))

  

  return {

    custo_mensal_atual: nova.pastTotalMonthlyCost,

    custo_mensal_novo: nova.totalMonthlyCost,

    delta: nova.diffTotalMonthlyCost,

    recursos_adicionados: nova.projects[0].diff.resources

      .filter(r => r.costCheck === 'new')

      .map(r => `${r.name} (+$${r.monthlyCost}/mês)`),

    recursos_removidos: [],

  }

}

Analisador de PR com LLM

async function analisarPR(titulo: string, descricao: string, arquivosAlterados: string[]): Promise<string> {

  const response = await client.messages.create({

    model: 'claude-haiku-4-5-20251001',

    max_tokens: 600,

    messages: [{

      role: 'user',

      content: `Analise este PR e forneça:

1. Tipo de mudança (feature/fix/refactor/docs)

2. Risco (baixo/médio/alto) com justificativa

3. Checklist de revisão (3-5 items)

4. Sugestão de reviewers por área



PR: ${titulo}

Descrição: ${descricao}

Arquivos: ${arquivosAlterados.join(', ')}`,

    }],

  })

  return response.content[0].type === 'text' ? response.content[0].text : ''

}

O analisador identifica que mudanças em terraform/ exigem revisor de infra, mudanças em src/db/ exigem DBA, e assim por diante.

Workflow YAML Gerado — Exemplo

# Gerado por: gerarWorkflow('Node.js 20, Jest, TypeScript, deploy ECS se main')

name: CI/CD Pipeline

on:

  push:

    branches: [main, develop]

  pull_request:

    branches: [main]



jobs:

  test:

    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 test -- --coverage

    - run: npm run build



  deploy:

    needs: test

    if: github.ref == 'refs/heads/main'

    runs-on: ubuntu-latest

    steps:

    - uses: aws-actions/configure-aws-credentials@v4

    - run: aws ecs update-service --cluster prod --service api --force-new-deployment

Exemplos Anotados

Exemplo 1: Pipeline Completo com 2 Gates

async function main() {

  // 1. Gerar workflow

  const workflow = await gerarWorkflow('Node.js 20, Jest com cobertura 80%, deploy no ECS se main')

  console.log(workflow.slice(0, 300))

  

  // 2. Gate de qualidade

  const testes = simularResultadoTestes(80)

  console.log(`Testes: ${testes.passando}/${testes.total}, cobertura ${testes.cobertura}%`)

  const resultadoQualidade = await gateQualidade(testes, 80)

  console.log(resultadoQualidade)

  

  // 3. Gate de custo

  const custo = simularInfracost()

  console.log(`Delta: +$${custo.delta}/mês`)

  const resultadoCusto = await gateCusto(custo, 100)

  console.log(resultadoCusto)

  

  // 4. Se ambos aprovados, analisar PR

  if (resultadoQualidade.includes('✅') && !resultadoCusto.includes('❌')) {

    const analise = await analisarPR(

      'feat: adicionar read replica',

      'Melhoria de performance de leitura',

      ['terraform/rds.tf', 'src/db/connection.ts']

    )

    console.log(analise)

  }

}

Exemplo 2: TODOs 1 e 2 testando casos extremos

// TODO 1: testes falhando

const testesRuins: ResultadoTestes = { passou: false, total: 150, passando: 145, falhando: 5, cobertura: 85, duracao_segundos: 60 }

console.log(await gateQualidade(testesRuins))

// → ❌ GATE REPROVADO: 5 teste(s) falhando



// TODO 1: cobertura baixa

const testesCobertura: ResultadoTestes = { passou: false, total: 150, passando: 150, falhando: 0, cobertura: 72, duracao_segundos: 55 }

console.log(await gateQualidade(testesCobertura, 80))

// → ❌ GATE REPROVADO: cobertura 72% abaixo do mínimo de 80%



// TODO 2: delta alto

const custoAlto = { custo_mensal_atual: 1000, custo_mensal_novo: 1200, delta: 200, recursos_adicionados: ['aws_rds (+$200)'], recursos_removidos: [] }

console.log(await gateCusto(custoAlto, 100))

// → ❌ GATE DE CUSTO REPROVADO: Delta $200 excede o limite de $100



// TODO 2: delta médio

const custoMedio = { custo_mensal_atual: 1000, custo_mensal_novo: 1070, delta: 70, recursos_adicionados: ['aws_elasticache (+$70)'], recursos_removidos: [] }

console.log(await gateCusto(custoMedio, 100))

// → ⚠️ AVISO DE CUSTO: Delta $70 está em 70% do limite

Padrões e Armadilhas

Padrões

Padrão 1: Gate com múltiplos critérios acumula erros

const problemas: string[] = []

if (resultado.falhando > 0) problemas.push(`${resultado.falhando} falhas`)

if (resultado.cobertura < minimo) problemas.push(`cobertura ${resultado.cobertura}%`)

// Retorna TODOS os problemas, não só o primeiro

if (problemas.length > 0) return `❌ GATE REPROVADO:\n${problemas.join('\n')}`

Mostrar todos os problemas de uma vez evita ciclos de “fix one, find another”.

Padrão 2: Thresholds em cascata com else if

if (delta > limite) return `❌ BLOQUEADO`

else if (delta > limite * 0.5) return `⚠️ AVISO`

else return `✅ APROVADO`

Padrão 3: Mensagem de gate inclui dados concretos

// RUIM

return '❌ Falhou'



// BOM — o desenvolvedor sabe exatamente o que precisa corrigir

return `❌ GATE REPROVADO: 3 testes falhando, cobertura 72% (mínimo: 80%)`

Armadilhas

⚠️ Armadilha 1: Gate com await desnecessário

// gateQualidade não precisa ser async se não chamar nada assíncrono

async function gateQualidade(resultado: ResultadoTestes): Promise<string> {

  // sem await aqui... o async é desnecessário

  return resultado.falhando > 0 ? '❌' : '✅'

}

// Pode ser função síncrona — async só se usar await dentro

⚠️ Armadilha 2: simularResultadoTestes com Math.random() — não é determinístico

// Em testes automatizados, use seed ou mock

const resultadoFixo: ResultadoTestes = { passou: false, falhando: 3, cobertura: 78, ... }

⚠️ Armadilha 3: Gate de custo com delta negativo (redução de custo)

// Se delta < 0 (infra ficou mais barata), sempre aprovar

if (estimativa.delta <= 0) {

  return `✅ APROVADO: redução de $${Math.abs(estimativa.delta).toFixed(2)}/mês`

}

// Sem esse check, delta negativo pode acionar o "aviso" incorretamente
⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

2 TODOs no starter.ts:

TODO 1 — gateQualidade(resultado, cobertura_minima):

const problemas: string[] = []

if (resultado.falhando > 0) problemas.push(`${resultado.falhando} teste(s) falhando`)

if (resultado.cobertura < cobertura_minima) problemas.push(`cobertura ${resultado.cobertura}% < ${cobertura_minima}%`)

if (problemas.length > 0) return `❌ GATE REPROVADO:\n${problemas.map(p => `  • ${p}`).join('\n')}`

return `✅ GATE APROVADO: ${resultado.passando}/${resultado.total} testes, ${resultado.cobertura}% cobertura`

TODO 2 — gateCusto(estimativa, limite_delta_usd):

const { delta } = estimativa

if (delta > limite_delta_usd) return `❌ GATE REPROVADO: delta $${delta} > limite $${limite_delta_usd}`

if (delta > limite_delta_usd * 0.5) return `⚠️ AVISO: delta $${delta} (${Math.round(delta/limite_delta_usd*100)}% do limite)`

return `✅ APROVADO: delta $${delta}/mês`

Rode com npx ts-node starter.ts e observe workflow gerado, gates avaliados e análise de PR.

Agora você está pronto para o lab.