Ir para o conteúdo
← Todas as matérias
  • Rust
  • Python
  • Git

Seshat: de Python para Rust

O Seshat gera commits convencionais com IA. Conto por que reescrevi a CLI em Rust e como decidi a migração, com matriz de paridade.

Junior Martins · 5 min de leitura

O Seshat é uma CLI que lê o diff staged, gera uma mensagem no padrão Conventional Commits usando IA e executa o git commit. Comecei em Python. Desde 10/04/2026, a versão principal é Rust.

Este post explica o que a ferramenta faz, por que reescrevi e, principalmente, como decidi que a versão Rust podia assumir.

O que o Seshat faz

O fluxo básico é simples: você faz git add, roda seshat commit e a ferramenta propõe a mensagem. Em volta disso, ela acumulou bastante coisa:

  • Providers HTTP: OpenAI, Codex API, DeepSeek, Anthropic Claude, Gemini, Z.ai e Ollama.
  • Providers CLI: Codex CLI e Claude CLI. Esses dois e o Ollama não exigem API_KEY global.
  • Commits sem IA: quando tudo o que está staged é Markdown, imagem, lock file, dotfile ou deleção, a mensagem sai automática, sem chamar provider. Dá para estender com commit.no_ai_extensions e commit.no_ai_paths.
  • Checks antes do commit: lint, test e typecheck, bloqueantes ou não.
  • Code review por IA: findings [BUG] e [SECURITY] podem bloquear o commit. Uma segunda IA, o JUDGE, pode reavaliar cada item.
  • Flow: processa vários arquivos e gera um commit por arquivo, com lock por arquivo para evitar colisão entre agentes.
  • GPG: com commit.gpgsign=true, valida a autenticação antes de chamar a IA. Se o pinentry falhar, falha cedo.

A versão Python usava Typer e Rich e era instalada com pipx. A versão Rust usa clap e gera um binário chamado seshat.

Por que reescrever em Rust

O objetivo ficou registrado no plano de migração: substituir a implementação Python por um binário Rust confiável, manter o comportamento público da CLI, reduzir o risco operacional em commits reais e deixar uma base mais simples de testar, distribuir e evoluir.

Não tem número de desempenho nessa lista. A decisão não partiu de benchmark de velocidade, e não vou inventar um aqui.

O ponto central é risco. O Seshat mexe no seu histórico do Git. Um bug nele vira um commit errado no repositório de alguém. Por isso, a regra número um do plano era preservar o comportamento antes de melhorar o design interno.

O plano também dizia, com todas as letras, que aquilo não era um rewrite livre. A versão Python seria a referência de comportamento até cada fluxo ter teste de paridade ou uma decisão explícita de mudança.

Como a migração foi decidida

Matriz de paridade

A primeira fase foi transformar o comportamento Python em uma matriz objetiva. Comandos, flags, campos do .seshat, variáveis de ambiente, arquivos lidos e escritos, efeitos colaterais no Git. Cada item recebeu um status:

StatusSignificado
portedcomportamento portado e coberto ou equivalente
partialexiste, mas falta cobertura ou detalhe
missingainda não existe no Rust
changeddifere de propósito ou por pendência registrada

O critério de aceite da fase era direto: nenhum comportamento importante fica só na memória de quem mantém. Todo gap vira um item rastreável.

Os testes Python também foram mapeados para testes Rust equivalentes. Git é tratado como dependência real: os testes end-to-end criam repositórios temporários e conferem o commit gerado. Providers são testados com transporte HTTP fake e executáveis fake, sem chamada real.

Definição de pronto

A migração só contava como concluída quando, entre outros pontos:

  • todo fluxo documentado da versão Python tivesse equivalente em Rust ou uma decisão documentada de mudança;
  • cargo fmt, cargo clippy e cargo test passassem;
  • houvesse testes end-to-end para commit, flow, init, fix e config;
  • o binário pudesse ser instalado e usado como seshat.

O que mudou de propósito

Três itens ficaram como changed, cada um com motivo registrado:

ItemDiferençaMotivo
commit sem .seshatRust falha direto; Python oferecia init interativoreduzir surpresa em automação
Tema da UIRust ainda não aplica tema customizadodocumentado como futuro; force_rich e icons funcionam
Detecção de Rust no toolingRust detecta Cargo.tomlnecessário para o próprio port

Repositórios separados

A decisão de corte tem data: 2026-04-10. O racional:

  • a CLI Rust cobre os comandos públicos commit, config, init, fix e flow;
  • a matriz de paridade não aponta lacuna funcional sem decisão registrada;
  • Git, providers, tooling, JSONL, UI e GPG estão cobertos por testes Rust.

Duas alternativas foram rejeitadas. Transformar o Python em wrapper do Rust misturaria responsabilidades entre repositórios. Congelar ou arquivar o Python contrariava a estratégia de manter implementações separadas por linguagem.

Ficou assim: o Rust é a fonte de verdade da versão Rust. O Python continua como repositório independente, como referência histórica e para comparar comportamento. No GitHub, ele está na branch main-py. Não houve corte destrutivo.

Qual seshat fica no seu PATH é problema de instalação, não de código. Você escolhe por PATH, pacote, alias ou gerenciador de versão.

Usando a versão Rust

Instalação direto do repositório:

cargo install --git https://github.com/juniormartinxo/seshat

Se você clonou o repositório, use cargo install --path . na raiz dele. Publicação no crates.io e instaladores nativos estão fora do escopo atual.

Crie a configuração do projeto. O commit exige .seshat/config.yaml:

seshat init --path . --force

Configure provider e idioma globais:

seshat config --provider codex
seshat config --language PT-BR

Gere e confirme o commit:

git add src/main.rs
seshat commit --yes

Rode checks antes do commit:

seshat commit --yes --check lint
seshat commit --yes --check full

Aplique os fixes configurados:

seshat fix
seshat fix --all

Processe arquivos em lote, um commit por arquivo:

seshat flow 3 --yes
seshat flow 3 --yes --check lint

Para automação, o commit emite JSON Lines com os eventos message_ready, committed, cancelled e error:

seshat commit --format json --yes

Um .seshat/config.yaml mínimo:

project_type: rust
commit:
  provider: codex
  language: PT-BR
  no_ai_extensions:
    - .md
checks:
  lint:
    enabled: true
    blocking: true
    command: "cargo fmt -- --check"
    fix_command: "cargo fmt"

Migrando de Python

Se você já usava a versão Python, o roteiro que documentei é este:

  1. Instale o binário Rust.
  2. Rode seshat --help e confirme que o seshat do PATH é o novo.
  3. Em cada projeto, rode seshat init --path . --force ou revise o .seshat existente.
  4. Valide um commit sem IA, com Markdown ou lock file.
  5. Valide um commit com IA no provider escolhido.
  6. Valide seshat fix e seshat flow, se fizerem parte do seu fluxo.

Modelo local

Também treinei um modelo para o Seshat: o seshat-commit, um LoRA sobre o Qwen 2.5 Coder 7B, que gera mensagens em PT-BR no padrão Conventional Commits. Roda pelo Ollama, sem custo de API:

ollama pull juniormartinxo/seshat-commit
seshat config --provider ollama --model juniormartinxo/seshat-commit
seshat commit --yes

Extensão do VS Code

Para quem prefere não sair do editor, existe o Seshat VSCode. A extensão chama a CLI seshat e preenche a caixa de mensagem do Source Control com o commit gerado.

O uso: faça stage dos arquivos e clique no ícone ✨ no cabeçalho do painel do Git, ou use Ctrl+Alt+S (Cmd+Alt+S no macOS). A CLI precisa estar no PATH. Se não estiver, configure o caminho em seshat.executablePath.

Por baixo, a extensão roda seshat commit --format json e lê os eventos JSONL, como message_ready e committed. É o mesmo contrato que a versão Rust documenta e testa.

A decisão completa, com racional e política de corte, está em site/content/cutover-decision.md no repositório. A matriz de paridade fica em site/content/parity-matrix.md.

Escrito por Junior Martins

Os vídeos do canal mostram estes projetos funcionando.

Inscreva-se no canal