Conexão · Interrompida

Algo não carregou

Parte desta página não chegou até você. Recarregue para tentar novamente — se persistir, verifique sua conexão.

Pular para o conteúdo principal
Engineering8 min de leitura

Transformando Manutenção de Repositório em Markdown: Mantendo uma Codebase Rust Viva com Workflows Agênticos

Repositórios de vida longa sofrem drift: componentes depreciados persistem, camadas sangram e testes não cobrem as funções que de fato quebram. No meu próprio estudo, transformei três tarefas recorrentes em workflows markdown agendados que o próprio repositório roda sobre si mesmo, e depois escrevi o que aprendi sobre limitar o raio de impacto, parear checagens LLM com scans determinísticos, e deixar os agentes rascunharem formatos enquanto eu escrevo a substância.

Todos os Posts
2/4

Codebases de vida longa nunca falham em um único dia. Elas sofrem drift. Um componente de substituição é entregue e metade do projeto ainda chama o depreciado. Um handler desce três camadas porque esse era o caminho mais curto sob prazo. Testes existem, mas não nas funções que de fato quebraram no último trimestre. Nada disso bloqueia uma release, nada disso merece um ticket, e tudo isso onera toda mudança futura. O problema que eu continuava enfrentando em meus próprios projetos paralelos não era falta de consciência — era falta de um loop que faria a consciência agir sobre si mesma.

O artigo de Kief Morris de março de 2026 no martinfowler.com me deu a moldura que me faltava: a alavanca produtiva com agentes de código é o loop de trabalho, não o prompt. Ele chama isso de estar "on the loop" — humanos projetam e mantêm o harness, agentes o rodam. Eu estava oscilando entre copiar-colar prompts em uma janela de chat e rodar scripts ad-hoc que só eu lembrava de rodar. Ambos são padrões in-the-loop que quebram no momento em que eu olho para o outro lado.

O que eu queria era um harness que o repositório pudesse carregar nas próprias costas. O GitHub lançou GitHub Agentic Workflows (gh aw) como technical preview em 13 de fevereiro de 2026, e o formato encaixou: cada workflow é um arquivo markdown sob .github/aw/, o agente roda com um token read-only por default, e qualquer escrita de volta no repositório deve passar por um bloco declarado safe-outputs que abre um pull request em um job separado e com escopo. O markdown é o harness. Isso mudou como eu pensei sobre tarefas de manutenção.

As três tarefas que tiro da minha cabeça

Em um serviço Rust que mantenho para estudo, três tarefas recorrentes continuavam aparecendo como itens "algum dia" nas minhas notas. Transformei cada uma em um workflow agendado.

A primeira é migração de componente depreciado. Um módulo de UI havia se movido de ui::button::Button para ui::v2::Button, e vinte e oito callers ainda usavam o antigo. O arquivo de workflow:

markdown
---
description: Migrate ui::button::Button to ui::v2::Button
on:
  schedule:
    - cron: "0 4 * * 2"
permissions: read-all
tools:
  - rg
  - cargo
safe-outputs:
  create-pull-request:
    max: 1
    labels: [chore, auto-migration]
---
Find up to 20 call sites of `ui::button::Button` that are not inside
`src/ui/button/`. Replace each with `ui::v2::Button`, preserving the
prop names listed in `docs/migration-button-v2.md`. Run
`cargo check --all-targets` and stop if it fails. Open one pull
request titled `chore(ui): migrate Button v1 -> v2 (N/28)`.

Dois detalhes importam. O max: 1 em create-pull-request limita o raio de impacto — se o agente interpretar mal o guia de migração, eu ganho um PR barulhento, não vinte. O limite de "até 20 call sites" mantém qualquer PR individual revisável; eu prefiro fazer merge de quinze PRs pequenos do que tentar ler um enorme.

A segunda tarefa é imposição de fronteiras arquiteturais. O serviço tem um layering limpo — domain, application, infrastructure — e a regra é que domain não deve importar de infrastructure. Um scan determinístico pega os casos fáceis; uma análise por LLM é o que finalmente pega os casos espertos, em que um módulo de domínio importa um helper cuja dependência transitiva alcança a infraestrutura. O Thoughtworks Technology Radar Volume 34 (abril de 2026) colocou "redução de drift arquitetural com LLMs" em Assess — promissor, ainda não um default. Meu workflow espelha esse padrão: rode o scan determinístico primeiro, entregue os casos ambíguos remanescentes para o agente.

markdown
---
description: Flag and propose fixes for domain -> infrastructure leaks
on:
  schedule:
    - cron: "0 5 * * 4"
permissions: read-all
tools:
  - rg
  - cargo
safe-outputs:
  create-issue:
    max: 3
    labels: [architecture, drift]
---
Scan `src/domain/**/*.rs`. For each module that directly or
transitively depends on a type from `src/infrastructure/**`,
propose a trait in `src/domain` that narrows the dependency.
Open at most three issues; each must cite the offending import
path and a minimal trait sketch. Do not open pull requests.

A terceira é fechamento de lacuna de cobertura. cargo tarpaulin --print-summary reporta cobertura por arquivo; o workflow escolhe as três funções públicas com zero cobertura de branch em arquivos abaixo de 60 por cento e propõe testes determinísticos — table-driven, sem I/O, sem clock. A saída vai para um PR com max: 1 para que eu possa rejeitar um teste ruim sem vadear por um backlog.

O caso que me fez confiar no loop

O workflow de arquitetura expôs um vazamento real que eu continuaria perdendo. Uma função de domínio dependia de uma struct de infrastructure::postgres::rows para moldar seu valor de retorno, e o scan determinístico perdeu porque a cadeia de imports passava por um re-export. O agente sinalizou o caminho ofensor e rascunhou um sketch de trait. Eu mantive o formato e escrevi o código final eu mesmo. Aqui está o exemplo mínimo que puxei das minhas notas para capturar o padrão para um eu-futuro:

rust
// src/domain/pricing.rs
// Run with: cargo test

pub struct Money { pub cents: i64 }

pub trait PriceSource {
    fn unit_price(&self, sku: &str) -> Option<Money>;
}

pub fn total(src: &dyn PriceSource, cart: &[(&str, u32)]) -> Money {
    let cents = cart
        .iter()
        .filter_map(|(sku, qty)| src.unit_price(sku).map(|p| p.cents * *qty as i64))
        .sum();
    Money { cents }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::collections::HashMap;

    struct Stub(HashMap<&'static str, i64>);
    impl PriceSource for Stub {
        fn unit_price(&self, sku: &str) -> Option<Money> {
            self.0.get(sku).map(|c| Money { cents: *c })
        }
    }

    #[test]
    fn totals_known_skus_and_ignores_unknown() {
        let src = Stub(HashMap::from([("A", 100), ("B", 250)]));
        let t = total(&src, &[("A", 2), ("B", 1), ("C", 5)]);
        assert_eq!(t.cents, 450);
    }
}

cargo test exercita o contrato inteiro sem tocar em Postgres, sem um runtime tokio, e sem a dependência transitiva chrono que o formato antigo arrastava. A trait PriceSource é a costura estreita. Antes da extração, cada mudança na struct de row da infraestrutura ressoava nos testes de domínio; depois, o domínio é dono do próprio vocabulário e o adapter Postgres implementa a trait em src/infrastructure/pricing_pg.rs. O agente não escreveu este código. Ele rascunhou a assinatura da trait em um comentário de issue e deixou a implementação para um PR humano. Essa divisão — agente propõe forma, eu escrevo substância — é a divisão que quero que o harness imponha.

O que errei na primeira passada

Algumas armadilhas que sofri nos meus próprios testes e corrigi:

  • Comecei com max: 5 no PR de migração. A atenção de revisão colapsou no PR três. Reduzir para max: 1 com um limite por execução de 20 arquivos fez com que cada PR ficasse pequeno o suficiente para ler em cinco minutos.
  • Dei ao workflow de arquitetura write-all "só por garantia". Previsivelmente, o agente propôs uma reescrita abrangente. Mudar para read-only mais safe-outputs: create-issue forçou a divisão rascunho-depois-humano e cortou o ruído por uma ordem de magnitude.
  • Agendei todos os três workflows no mesmo minuto. A feature de fuzzy-schedule do gh-aw distribui horários de início de forma determinística entre workflows para evitar picos de cron; sair de um horário exato compartilhado me fez parar de brigar comigo mesmo por minutos de runner.
  • Assumi que HTTP outbound do agente era irrestrito. Não é — o runtime roteia tráfego outbound através de um proxy com uma allowlist explícita de domínios, e qualquer coisa fora dela é descartada no kernel. Isso é uma feature; parei de escrever workflows que tentavam chamar analisadores SaaS arbitrários.

Quando usar isso, quando deixar de lado

Esse formato se paga quando a tarefa é recorrente, estreita e fácil de revisar. Migrações de símbolo depreciado, drift de fronteiras de import, lacunas de cobertura em funções puras, freshness de doc-comment — todas encaixam. O custo é um punhado de PRs pequenos por semana, e cada um carrega a mesma carga mental de uma limpeza escrita por humano.

É o formato errado quando a tarefa é pontual, quando requer gosto ("deixe essa API mais bonita"), ou quando o modo de falha é sutil o bastante para que um PR barulhento possa passar despercebido na revisão. A imposição de arquitetura guiada por LLM ainda está em Assess no Radar da Thoughtworks por boa razão — eu a pareio com regras determinísticas e trato a saída do agente como rascunho, nunca veredito.

O único takeaway ao qual fico voltando: um repositório que se mantém sob supervisão, não por interrupção, devolve a largura de banda que eu costumava gastar lembrando de agendar sprints de limpeza.

Notas para um eu-futuro:

  • mantenha safe-outputs.*.max pequeno (1–3), e limite o escopo por execução dentro do próprio prompt.
  • use permissões read-only; nunca defina write-all para economizar um minuto de debug.
  • use fuzzy schedules para que workflows paralelos não colidam em runners.
  • pareie qualquer passo de imposição via LLM com um scan determinístico que roda primeiro.
  • deixe o agente propor formatos em issues; escreva a substância em PRs.

Use quando: tarefas recorrentes e estreitas com revisão barata. Evite quando: julgamento de gosto, pontuais, ou qualquer lugar em que uma resposta errada silenciosa possa aterrissar.


Leitura adicional: Kief Morris, "Humans and Agents in Software Engineering Loops", martinfowler.com, 4 de março de 2026; a visão geral do gh-aw e a referência de safe-outputs em github.github.com/gh-aw; Thoughtworks Technology Radar Volume 34, abril de 2026.

Continue lendo

Curtindo? Talvez goste disso aqui.

Nada parecido — quer tentar outro ângulo?

Isso foi útil?

Deixe uma avaliação ou uma nota rápida — me ajuda a melhorar.