Bot de Atualização de Dependências
Escaneia pacotes desatualizados. Executa auditoria de segurança. Obtém changelogs. Resume mudanças críticas. Abre um PR por grupo de risco.
Regra crítica: Atualize apenas os pacotes que o comando outdated do gerenciador de pacotes realmente reportar. Nunca adivinhe ou invente números de versão. Se um changelog não puder ser obtido, anote a lacuna em vez de inventar conteúdo.
Etapa 1: Verificação de Configuração
echo "GEMINI_API_KEY: ${GEMINI_API_KEY:+set}"
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-não definido, busca de changelogs com limite de 60/hora}"
gh auth status 2>/dev/null | head -1 || echo "gh: não autenticado"
Se GEMINI_API_KEY estiver faltando: Pare. Informe ao usuário: "GEMINI_API_KEY é necessária. Obtenha em aistudio.google.com. Adicione ao seu arquivo .env."
Se gh não estiver autenticado: Pare. Informe ao usuário: "O GitHub CLI precisa ser autenticado. Execute: gh auth login"
Detecte o(s) gerenciador(es) de pacotes:
ls package.json 2>/dev/null && echo "npm"
ls requirements.txt pyproject.toml 2>/dev/null && echo "pip"
ls Cargo.toml 2>/dev/null && echo "cargo"
ls go.mod 2>/dev/null && echo "go"
ls Gemfile 2>/dev/null && echo "ruby"
Se múltiplos forem encontrados, pergunte: "Encontrado(s) [lista]. Qual devo escanear? (all / npm / pip / cargo / go / ruby)"
Etapa 2: Detectar Pacotes Desatualizados
npm:
npm outdated --json --long 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
for name, info in data.items():
print(json.dumps({'name': name, 'current': info.get('current','?'), 'latest': info.get('latest','?'), 'dep_type': info.get('type','dependencies')}))
"
pip:
pip list --outdated --format=json 2>/dev/null | python3 -c "
import sys, json
for p in json.load(sys.stdin):
print(json.dumps({'name': p['name'], 'current': p['version'], 'latest': p['latest_version']}))
"
Cargo (Rust):
cargo outdated --format json 2>/dev/null || \
cargo outdated 2>/dev/null | grep -v "^---" | tail -n +3 | head -30
# Se cargo-outdated não estiver instalado: cargo install cargo-outdated
Módulos Go:
go list -u -m -json all 2>/dev/null | python3 -c "
import sys, json
decoder = json.JSONDecoder()
buf = sys.stdin.read()
pos = 0
while pos < len(buf):
try:
obj, idx = decoder.raw_decode(buf, pos)
if obj.get('Update'):
print(json.dumps({'name': obj['Path'], 'current': obj['Version'], 'latest': obj['Update']['Version']}))
pos += idx
except: break
"
Ruby (Bundler):
bundle outdated --parseable 2>/dev/null | python3 -c "
import sys
for line in sys.stdin:
parts = line.strip().split()
if len(parts) >= 4:
print('{\"name\":\"' + parts[0] + '\",\"current\":\"' + parts[3].strip('()') + '\",\"latest\":\"' + parts[1] + '\"}')
"
Se todos retornarem vazio: "Todos os pacotes estão atualizados." Pare.
Informe a contagem antes de prosseguir: "Encontrados X pacotes desatualizados."
Etapa 3: Classificar por Nível de Risco
Analise o incremento de versão (atual → mais recente):
- MAJOR: primeiro dígito alterado (1.x.x → 2.x.x)
- MINOR: segundo dígito alterado (1.2.x → 1.3.x)
- PATCH: terceiro dígito alterado (1.2.3 → 1.2.4)
python3 -c "
def classify(current, latest):
try:
c = [int(x) for x in current.lstrip('v').split('.')[:3]]
l = [int(x) for x in latest.lstrip('v').split('.')[:3]]
if l[0] > c[0]: return 'major'
if len(l) > 1 and len(c) > 1 and l[1] > c[1]: return 'minor'
return 'patch'
except: return 'unknown'
"
Informe a distribuição: "Patch: X pacotes. Minor: Y pacotes. Major: Z pacotes."
Etapa 4: Auditoria de Segurança
Execute uma varredura de CVE antes de criar qualquer PR. Isso determina a urgência.
npm:
npm audit --json 2>/dev/null | python3 -c "
import sys, json
d = json.load(sys.stdin)
vulns = d.get('vulnerabilities', {})
for pkg, info in vulns.items():
sev = info.get('severity', 'unknown')
via = [v.get('title','') for v in info.get('via',[]) if isinstance(v, dict)]
print(f' [{sev.upper()}] {pkg}: {via[0] if via else \"veja npm audit\"}')
" 2>/dev/null || echo "Nenhuma vulnerabilidade encontrada ou npm audit não disponível"
pip:
pip-audit --format=json 2>/dev/null | python3 -c "
import sys, json
for vuln in json.load(sys.stdin):
print(f' [{vuln.get(\"aliases\",[\"\"])[0]}] {vuln[\"name\"]} {vuln[\"version\"]}: {vuln[\"description\"][:80]}')
" 2>/dev/null || echo "pip-audit não instalado. Execute: pip install pip-audit"
Cargo:
cargo audit 2>/dev/null | grep -E "^(ID|Package|Severity|URL)" | head -30 \
|| echo "cargo-audit não instalado. Execute: cargo install cargo-audit"
Regra de escalonamento: Se uma atualização PATCH ou MINOR tiver um CVE Crítico ou Alto, promova-a para prioridade MAJOR: ela receberá seu próprio PR e os detalhes do CVE irão no corpo do PR.
Relate os achados de segurança antes de prosseguir:
Auditoria de segurança: [N] vulnerabilidades encontradas
[CRÍTICO] lodash 4.17.19: Prototype Pollution (CVE-2021-23337)
[ALTO] axios 0.21.1: Server-Side Request Forgery (CVE-2021-3749)
Se não houver vulnerabilidades: "Auditoria de segurança: limpa."
Etapa 5: Obter Changelogs
Para cada pacote, tente as fontes em ordem. Pare na primeira que retornar conteúdo.
Fonte 1: API de Releases do GitHub
Obtenha a URL do repositório a partir do registro:
# npm
curl -s "https://registry.npmjs.org/{PACKAGE}/latest" \
| python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('repository',{}); print(r.get('url','') if isinstance(r,dict) else str(r))"
# pip
curl -s "https://pypi.org/pypi/{PACKAGE}/json" \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('info',{}).get('home_page','') or d.get('info',{}).get('project_urls',{}).get('Source',''))"
Obtenha as últimas 5 releases:
AUTH_HEADER=""
[ -n "$GITHUB_TOKEN" ] && AUTH_HEADER="-H \"Authorization: Bearer $GITHUB_TOKEN\""
curl -s $AUTH_HEADER \
"https://api.github.com/repos/{OWNER}/{REPO}/releases?per_page=5" \
| python3 -c "import sys,json; [print(json.dumps({'tag':r.get('tag_name',''),'body':r.get('body','')[:1500]})) for r in json.load(sys.stdin)]"
Mantenha apenas as releases entre a versão atual e a mais recente.
Fonte 2: README do registro npm (fallback)
curl -s "https://registry.npmjs.org/{PACKAGE}" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('readme','')[:3000])"
Fonte 3: Descrição do PyPI (último recurso para pip)
curl -s "https://pypi.org/pypi/{PACKAGE}/json" \
| python3 -c "import sys,json; print(json.load(sys.stdin).get('info',{}).get('description','')[:2000])"
Se nenhuma fonte retornar conteúdo: anote "Nenhum changelog encontrado" e continue.
Etapa 6: Resumir com Gemini
Uma requisição por grupo de risco. Inclua achados de segurança para quaisquer pacotes afetados por CVE:
cat > /tmp/deps-summary-request.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "Você é um desenvolvedor escrevendo uma descrição de PR do GitHub para uma atualização de dependência. Dada uma lista de pacotes sendo atualizados e o conteúdo bruto de seus changelogs, escreva um corpo de PR conciso em Markdown. Regras: Para cada pacote, liste apenas o que mudou entre a versão ANTIGA e a NOVA. Use marcadores. Sinalize mudanças que quebram compatibilidade com um prefixo BREAKING. Sinalize correções de CVE com um prefixo SECURITY e inclua o ID do CVE. Mantenha cada seção de pacote com no máximo 3-5 marcadores. Se nenhum changelog foi encontrado para um pacote, escreva 'Nenhum changelog disponível.' Não use travessões. Não use estas palavras: perfeito, robusto, alavancar, transformar, inovador. Produza apenas o corpo do PR em Markdown, sem comentários."
}]
},
"contents": [{
"parts": [{
"text": "PACOTES_E_CHANGELOGS_AQUI"
}]
}],
"generationConfig": {
"temperature": 0.2,
"maxOutputTokens": 2048
}
}
ENDJSON
curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/deps-summary-request.json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['candidates'][0]['content']['parts'][0]['text'])"
Etapa 7: Criar PRs
Um PR por grupo de risco não vazio. Um PR por pacote para atualizações major (revisão individual necessária).
1. Crie um branch:
BRANCH="deps/{RISK}-updates-$(date +%Y%m%d)"
git checkout -b "$BRANCH"
2. Atualize o arquivo do pacote:
npm:
npm install {package}@{latest_version} --save-exact
# devDependencies:
npm install {package}@{latest_version} --save-dev --save-exact
pip:
python3 -c "
import re, sys
pkg, version, filename = sys.argv[1], sys.argv[2], sys.argv[3]
with open(filename) as f: content = f.read()
pattern = rf'^{re.escape(pkg)}[>=<!\\s].*$'
new_content = re.sub(pattern, f'{pkg}=={version}', content, flags=re.MULTILINE|re.IGNORECASE)
if new_content == content: new_content = content + f'\\n{pkg}=={version}'
open(filename, 'w').write(new_content)
" "{PACKAGE}" "{LATEST}" "requirements.txt"
Cargo:
# Edite o campo version no Cargo.toml para o pacote, depois:
cargo update {package}
Go:
go get {module}@{latest_version}
go mod tidy
Ruby:
bundle update {gem_name}
3. Commit:
git add -A
git commit -m "chore(deps): atualizar dependências {RISK} $(date +%Y-%m-%d)"
4. Crie o PR:
cat > /tmp/dep-pr-body-{RISK}.md << 'ENDMD'
CORPO_DO_PR_DO_GEMINI
ENDMD
gh pr create \
--title "chore(deps): atualizar dependências {RISK}" \
--body-file /tmp/dep-pr-body-{RISK}.md \
--label "dependencies" \
--base main
Atualizações major recebem a label dependencies,breaking-change. Atualizações que corrigem CVE recebem a label dependencies,security.
Após cada PR, retorne ao main: git checkout main
Etapa 8: Modo de Diagnóstico
Gatilho: Se qualquer comando de instalação de pacote falhar durante a execução, entre no Modo de Diagnóstico em vez de parar.
Detecte o tipo de falha:
| Padrão de erro | Causa provável | Correção sugerida |
|---|---|---|
peer dep conflict | Incompatibilidade de dependência par | Mostre o par conflitante, sugira a flag --legacy-peer-deps ou downgrade |
ERESOLVE | Conflito de resolução npm | Execute npm install --legacy-peer-deps apenas para o pacote afetado |
version not found | Versão não existe no registro | Verifique o registro com npm view {pkg} versions |
python requires | Incompatibilidade de versão do Python | Anote a versão do Python necessária, pule o pacote |
cargo E0463 | Incompatibilidade de edição Rust | Sinalize para revisão manual |
Apresente um resumo de diagnóstico:
Instalação falhou para {package}: {tipo de erro}
Causa provável: {explicação}
Correção sugerida: {comando ou ação específica}
Pacotes restantes: prosseguindo com {N} que tiveram sucesso.
Não pare toda a execução quando um pacote falhar. Continue com os pacotes que tiverem sucesso.
Etapa 9: Resumo da Saída
## Resumo de Atualização de Dependências: [YYYY-MM-DD]
### Segurança
[CRÍTICO] lodash: CVE-2021-23337 corrigido em 4.17.21: PR #42
[ALTO] axios: CVE-2021-3749 corrigido em 0.21.4: PR #42
| Nível de Risco | Pacotes | PR |
|----------------|---------|-----|
| Patch | lodash 4.17.19→4.17.21, axios 0.21.1→0.21.4 | #42 |
| Minor | express 4.17.1→4.18.2 | #43 |
| Major | react 17.0.2→18.2.0 | #44 |
PRs abertos: 3
Pacotes sem changelog: pacote-obscuro (sem repositório GitHub no registro)
Falhas de instalação: nenhuma
Próxima ação: Revise os PRs de atualizações major individualmente antes de mesclar.