Automação de stand-up diário com script que lê git logs e gera relatórios automáticos no terminal

Como Automatizei Meus Stand-ups Diários com Um Script que Lê Git Logs (E Economizo 25 Minutos Por Reunião)

Todo dia de manhã, mesma cena: 9h03, a call do stand-up abre. Alguém compartilha a tela do Jira. Quatro pessoas falam por 2 minutos cada. A quinta pessoa fala por 12 minutos sobre um bug que ninguém entendeu. Vinte e sete minutos depois, você volta pro código com a sensação de que poderia ter lido um email.

E se eu te dissesse que eu substituí essa reunião por um script de 80 linhas em Bash que lê meu Git log, organiza por projeto e gera um relatório formatado — pronto pra colar no Slack ou no email do time? Hoje eu vou te mostrar como automatizei meus stand-ups diários e economizo em média 25 minutos por reunião.

Código com syntax highlighting representando script de automação de stand-up diário

O Problema: Stand-ups Que Devoram Tempo

Vamos ser sinceros: a maioria dos stand-ups diários é um desperdício coletivo. A intenção é boa — alinhar o time, identificar bloqueios, criar accountability. Mas na prática, viram um ritual onde cada pessoa tenta lembrar o que fez ontem enquanto os outros fingem prestar atenção.

Eu trabalhava num time distribuído com 7 devs. Nosso stand-up levava em média 32 minutos. Três vezes por semana. Isso dá 1h36 por semana em stand-ups. Por ano? Mais de 80 horas só em atualizações de status.

O pior: eu percebi que eu passava os primeiros 5 minutos de cada stand-up tentando lembrar o que eu tinha feito. Literalmente abrindo o Git log no terminal enquanto a pessoa anterior falava. Se eu já estava consultando o Git, por que não automatizar isso?

A Ideia: Git Log Como Fonte de Verdade

O insight foi simples: seus commits já contam a história do que você fez. Cada mensagem de commit é um registro de atividade. O Git já armazena tudo — timestamps, arquivos modificados, mensagens descritivas. Só faltava alguém pra conectar os pontos.

Então eu construí um pipeline de três etapas:

  1. Coleta: um script que extrai commits do último período útil (ontem ou desde o último stand-up)
  2. Classificação: agrupa os commits por projeto/repo e categoriza (feature, fix, refactor, docs)
  3. Formatação: gera um relatório em Markdown pronto pra colar no Slack, Teams ou email

E o bônus que eu não esperava: o script me ajudou a perceber quando eu estava fazendo commits genéricos do tipo “fix bug” ou “update”. Me forçou a escrever mensagens melhores.

Conventional Commits: O Pré-Requisito Que Muda Tudo

Antes do script funcionar direito, eu precisei adotar um sistema que já usava pra produtividade: Conventional Commits. Se você não conhece, é um padrão de mensagens de commit que começa com um prefixo:

feat: adiciona validação de CPF no formulário de cadastro
fix: corrige race condition no pool de conexões
refactor: extrai lógica de parsing para módulo separado
docs: atualiza README com instruções de deploy
chore: atualiza dependências do projeto
test: adiciona testes de integração para API de pagamentos

Com esse padrão, o script consegue classificar automaticamente o tipo de trabalho. E a beleza é que, se você já usa ferramentas de automação, provavelmente já tem um hook de commit que valida isso.

O Script: standup-report.sh

Vamos ao que interessa. Aqui está o script completo, com comentários linha a linha:

#!/usr/bin/env bash
# standup-report.sh — Gera relatório de stand-up a partir do Git log
# Uso: ./standup-report.sh [repo_path] [horas_atras]

REPO_PATH="${1:-.}"
HOURS_AGO="${2:-24}"
OUTPUT_FORMAT="${3:-markdown}" # markdown, slack, plain

# Cores para terminal
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

cd "$REPO_PATH" || { echo "Repositório não encontrado: $REPO_PATH"; exit 1; }

# Verifica se é um repositório Git
if [ ! -d ".git" ]; then
    echo "Não é um repositório Git: $REPO_PATH"
    exit 1
fi

SINCE=$(date -d "$HOURS_AGO hours ago" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || \
        date -v-${HOURS_AGO}H '+%Y-%m-%d %H:%M:%S')

REPO_NAME=$(basename "$(git rev-parse --show-toplevel 2>/dev/null || echo 'unknown')")

echo "========================================="
echo "📋 STAND-UP REPORT — $REPO_NAME"
echo "📅 Período: desde $SINCE"
echo "========================================="
echo ""

# Extrai commits do período
COMMITS=$(git log --author="$(git config user.name)" \
    --since="$SINCE" \
    --pretty=format:'%h|%s|%ai' \
    --no-merges 2>/dev/null)

if [ -z "$COMMITS" ]; then
    echo "nenhum commit encontrado desde $SINCE."
    echo ""
    echo "💡 Dica: use 'standup-report.sh . 48' para os últimos 2 dias"
    exit 0
fi

# Classificação por tipo (Conventional Commits)
declare -A CATEGORIES
declare -A CATEGORY_EMOJI
CATEGORY_EMOJI=(
    ["feat"]="✨"
    ["fix"]="🐛"
    ["refactor"]="♻️"
    ["docs"]="📝"
    ["test"]="🧪"
    ["chore"]="🔧"
    ["style"]="💄"
    ["perf"]="⚡"
    ["other"]="📦"
)

TOTAL_COMMITS=0
TOTAL_ADDITIONS=0
TOTAL_DELETIONS=0

while IFS='|' read -r hash msg date; do
    TYPE="other"
    [[ "$msg" =~ ^([a-z]+)(\(.+\))?: ]] && TYPE="${BASH_REMATCH[1]}"
    CATEGORIES["$TYPE"]+="$hash|$msg|$date\n"
    ((TOTAL_COMMITS++))
    
    # Conta linhas modificadas
    STATS=$(git diff-tree --no-commit-id --numstat -r "$hash" 2>/dev/null | \
            awk '{ add += $1; del += $2 } END { print add, del }')
    read -r adds dels <<< "$STATS"
    TOTAL_ADDITIONS=$((TOTAL_ADDITIONS + ${adds:-0}))
    TOTAL_DELETIONS=$((TOTAL_DELETIONS + ${dels:-0}))
done <<< "$COMMITS"

# Relatório por categoria
for cat in feat fix refactor test docs chore perf style other; do
    if [ -n "${CATEGORIES[$cat]}" ]; then
        emoji="${CATEGORY_EMOJI[$cat]:-📦}"
        case $cat in
            feat) label="Features" ;;
            fix) label="Bug Fixes" ;;
            refactor) label="Refactors" ;;
            test) label="Tests" ;;
            docs) label="Documentação" ;;
            chore) label="Manutenção" ;;
            perf) label="Performance" ;;
            style) label="Estilo" ;;
            *) label="Outros" ;;
        esac
        
        echo -e "${emoji} ${label}:"
        echo -e "${CATEGORIES[$cat]}" | while IFS='|' read -r c_hash c_msg c_date; do
            [ -z "$c_hash" ] && continue
            echo "  • $c_msg (${c_hash})"
        done
        echo ""
    fi
done

# Resumo estatístico
echo "========================================="
echo -e "📊 Resumo:"
echo "  Commits: $TOTAL_COMMITS"
echo "  Linhas adicionadas: +$TOTAL_ADDITIONS"
echo "  Linhas removidas: -$TOTAL_DELETIONS"
echo "  Arquivos modificados: $(git diff --stat HEAD~$TOTAL_COMMITS..HEAD 2>/dev/null | tail -1 | awk '{print $1}')"
echo "========================================="

O script é deliberadamente simples. Sem dependências externas. Sem Python, sem Node. Só Bash e Git — ferramentas que todo dev já tem instaladas.

Integração com Slack e Teams

Gerar o relatório no terminal é só metade da diversão. A outra metade é enviar automaticamente pro canal do time. Para isso, eu criei um wrapper que formata a saída em blocos do Slack:

#!/usr/bin/env bash
# standup-slack.sh — Envia stand-up report para o Slack via webhook

SLACK_WEBHOOK="${SLACK_STANDUP_WEBHOOK:?Defina SLACK_STANDUP_WEBHOOK no .bashrc}"
REPO_PATH="${1:-.}"

# Gera o report em texto plano
REPORT=$(./standup-report.sh "$REPO_PATH" 24 plain)

# Conta commits por tipo
FEAT_COUNT=$(echo "$REPORT" | grep -c "^feat" || echo 0)
FIX_COUNT=$(echo "$REPORT" | grep -c "^fix" || echo 0)

# Monta payload do Slack
PAYLOAD=$(cat <<EOF
{
  "blocks": [
    {
      "type": "header",
      "text": {
        "type": "plain_text",
        "text": "☀️ Stand-up Automático — $(date +%d/%m/%Y)"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*O que fiz nas últimas 24h:*\n\`\`\`${REPORT}\`\`\`"
      }
    },
    {
      "type": "context",
      "elements": [
        {
          "type": "mrkdwn",
          "text": "✨ ${FEAT_COUNT} features · 🐛 ${FIX_COUNT} fixes · Gerado automaticamente"
        }
      ]
    }
  ]
}
EOF
)

curl -s -X POST "$SLACK_WEBHOOK" \
    -H 'Content-Type: application/json' \
    -d "$PAYLOAD" > /dev/null

echo "✅ Stand-up enviado pro Slack!"

Terminal com código colorido representando automação de produtividade e relatórios de stand-up

Cronjob: O Stand-up Que Se Escreve Sozinho

Agora a parte que realmente transforma isso em automação: um cronjob que roda antes do horário do stand-up. Meu stand-up era às 9h. O cronjob roda às 8h55:

# crontab -e
# Gera e envia stand-up report às 8:55 de segunda a sexta
55 8 * * 1-5 /home/user/scripts/standup-slack.sh /home/user/projects/api >> /tmp/standup.log 2>&1

Resultado: quando a call do stand-up abre às 9h, meu relatório já está no canal do Slack. Todo mundo lê em 30 segundos. A reunião que levava 32 minutos agora leva 7 — só pra discutir bloqueios reais, que é o que importa.

E se você já automatizou seu time blocking com scripts, pode integrar o stand-up report no seu bloco de manhã. Tudo flui junto.

Multi-Repo: Quando Você Trabalha em Vários Projetos

A maioria dos devs trabalha em mais de um repositório. Eu tinha 3 projetos ativos. A solução foi um wrapper que itera sobre todos os repos:

#!/usr/bin/env bash
# standup-multi.sh — Agrega stand-up de múltiplos repositórios

REPOS=(
    "$HOME/projects/api-backend"
    "$HOME/projects/web-frontend"
    "$HOME/projects/shared-libs"
    "$HOME/projects/infra-terraform"
)

FULL_REPORT=""

for repo in "${REPOS[@]}"; do
    if [ -d "$repo/.git" ]; then
        REPO_REPORT=$(standup-report.sh "$repo" 24 plain)
        if [ -n "$REPO_REPORT" ] && [ "$REPO_REPORT" != "nenhum commit encontrado."* ]; then
            REPO_NAME=$(basename "$repo")
            FULL_REPORT+="\n📁 $REPO_NAME:\n$REPO_REPORT\n"
        fi
    fi
done

if [ -z "$FULL_REPORT" ]; then
    echo "nenhuma atividade nos últimos 24h. Dia tranquilo. 🌴"
else
    echo -e "$FULL_REPORT"
fi

Esse wrapper vira o comando principal. Ele varre todos os diretórios de projeto, coleta commits de cada um e monta um relatório unificado. Quando eu chego de manhã e digito standup-multi.sh, recebo um panorama completo de tudo que fiz em todos os projetos.

O Que Aprendi Sobre Meus Hábitos de Código

Depois de 3 meses usando o script, os dados revelaram padrões que eu não enxergava:

  • Terça é meu dia de fix: 40% dos meus commits de segunda são “feat”, mas terça sempre tem pico de “fix”. Clássico: código novo na segunda, bugs na terça.
  • Eu subestimo refactoring: achava que refatorava bastante. Os dados mostravam que apenas 8% dos meus commits eram refactor. Agora é um KPI pessoal — tento manter acima de 15%.
  • Sexta à tarde é zona morta: depois das 15h de sexta, meus commits são quase todos “chore” e “docs”. Aceitei isso e parei de lutar contra.
  • Commits grandes = problemas: quando um commit toca mais de 15 arquivos, quase sempre introduz bug no dia seguinte. Agora eu splito proativamente.

🧗 Perrengue do Olivetto:

Uma segunda-feira, o script reportou 47 commits num dia. Quarenta e sete. Eu achei que tinha sido produtivo. Na verdade, eu tinha feito um rebase maluco que duplicou o histórico. O script estava contando commits que tecnicamente não existiam mais. Moral da história: sempre filtre por --no-merges e confira o reflog antes de confiar cegamente nos números. Automatizar é ótimo, mas validar a automação é essencial.

Bônus: Hook Pre-Commit Para Mensagens Melhores

Como bônus, o script te força a escrever commits melhores — porque ninguém quer ver “update” num relatório que vai pro Slack do time inteiro. Mas se você quiser garantir isso no nível do Git, um hook commit-msg simples:

# .git/hooks/commit-msg
MSG=$(cat "$1")
PATTERN="^(feat|fix|refactor|docs|test|chore|style|perf)(\(.+\))?: .{10,}"

if ! echo "$MSG" | grep -qE "$PATTERN"; then
    echo "❌ Commit message inválida!"
    echo ""
    echo "Formato: tipo(escopo): descrição (mín 10 chars)"
    echo "Tipos: feat, fix, refactor, docs, test, chore, style, perf"
    echo ""
    echo "Exemplo: feat(auth): adiciona login com 2FA via TOTP"
    exit 1
fi

Esse hook recusa commits com mensagens vagas. Se você já usa dashboards de métricas pessoais, pode rastrear a evolução da qualidade dos seus commits ao longo do tempo.

Antes e Depois: Os Números

Depois de implementar o stand-up automatizado no meu time, medimos o impacto real:

  • Duração média do stand-up: 32 min → 8 min (-75%)
  • Tempo de preparação: 5 min tentando lembrar → 0 min (automático)
  • Qualidade das atualizações: vagas e esquecidas → detalhadas com hashes de commit
  • Adoção pelo time: 6 de 7 devs adotaram o script em 2 semanas
  • Satisfação com stand-ups: de “perda de tempo” pra “momento útil de alinhamento”

A sétima pessoa? Ela prefere escrever manualmente. E tudo bem. A ideia não é forçar — é remover a fricção pra quem quer participar melhor.

Adaptando Pro Seu Contexto

O script que mostrei funciona pra devs que usam Git. Mas a filosofia se aplica a qualquer ferramenta:

  • Se usa Jira: a API REST do Jira permite buscar issues atualizadas no período. Substitua o Git log por um curl no Jira.
  • Se usa Trello: o mesmo. A API do Trello é amigável e retorna cards movidos entre lists.
  • Se usa Linear: a GraphQL API do Linear é perfeita pra isso.
  • Se usa Notion: mais complexo, mas as databases do Notion têm API de query por data.

O princípio é universal: sua ferramenta de trabalho já registra o que você faz. Use isso.

Próximos Passos

Se você curtiu a ideia, aqui vai um roadmap pra implementar hoje:

  1. Adote Conventional Commits — amanhã mesmo. É o pré-requisito pra tudo funcionar.
  2. Copie o script e adapte pro seu fluxo. Mude as categorias, ajuste as cores.
  3. Teste manualmente por uma semana antes de automatizar com cron.
  4. Adicione o webhook do Slack quando estiver confiante no output.
  5. Compartilhe com o time e veja quem adota naturalmente.

E se você quer ir além, dá pra integrar com IA para gerar resumos mais naturais — passar os commits por um modelo de linguagem que reescreve em português fluido. Mas isso é tema pra outro post.

E você? Qual reunião repetitiva você quer automatizar primeiro? Me conta nos comentários — ou melhor, me manda o script que você criou. A gente publica aqui. 😉

Posts Similares