- 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_KEYglobal. - 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_extensionsecommit.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:
| Status | Significado |
|---|---|
ported | comportamento portado e coberto ou equivalente |
partial | existe, mas falta cobertura ou detalhe |
missing | ainda não existe no Rust |
changed | difere 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 clippyecargo testpassassem;- houvesse testes end-to-end para
commit,flow,init,fixeconfig; - 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:
| Item | Diferença | Motivo |
|---|---|---|
commit sem .seshat | Rust falha direto; Python oferecia init interativo | reduzir surpresa em automação |
| Tema da UI | Rust ainda não aplica tema customizado | documentado como futuro; force_rich e icons funcionam |
| Detecção de Rust no tooling | Rust detecta Cargo.toml | necessá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,fixeflow; - 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/seshatSe 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 . --forceConfigure provider e idioma globais:
seshat config --provider codex
seshat config --language PT-BRGere e confirme o commit:
git add src/main.rs
seshat commit --yesRode checks antes do commit:
seshat commit --yes --check lint
seshat commit --yes --check fullAplique os fixes configurados:
seshat fix
seshat fix --allProcesse arquivos em lote, um commit por arquivo:
seshat flow 3 --yes
seshat flow 3 --yes --check lintPara automação, o commit emite JSON Lines com os eventos message_ready, committed, cancelled e error:
seshat commit --format json --yesUm .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:
- Instale o binário Rust.
- Rode
seshat --helpe confirme que oseshatdoPATHé o novo. - Em cada projeto, rode
seshat init --path . --forceou revise o.seshatexistente. - Valide um commit sem IA, com Markdown ou lock file.
- Valide um commit com IA no provider escolhido.
- Valide
seshat fixeseshat 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 --yesExtensã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.
Links
- Site e documentação: seshat.juniormartins.dev
- CLI em Rust: github.com/juniormartinxo/seshat
- Versão Python: branch
main-py - Extensão: github.com/juniormartinxo/seshat-vscode
- Modelo: ollama.com/juniormartinxo/seshat-commit
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.