Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Guia de desenvolvimento

Para o básico (clonar, compilar, rodar os testes, convenções) veja Contribuindo. Esta página vai além: dois agentes na mesma máquina, o servidor MCP dirigido à mão, diagnóstico de rede, validação da documentação e a mecânica de um release. Comandos, mapa dos arquivos e invariantes ficam no AGENTS.md.

Requisitos

FerramentaPara quê
Rust 1.91 ou mais novomínimo do iroh 1.3 (o job msrv do CI confere); edição 2024 e File::try_lock
gh (GitHub CLI)disparar workflows, baixar artefatos, criar releases
mdBook 0.5.4livro em docs/ (a mesma versão fixada no workflow pages)
Chromiumvalidar diagramas Mermaid e tirar prints do site
Python 3scripts em scripts/

Builds para outras plataformas (macOS, Windows, Linux ARM64 e musl) saem do CI; localmente basta o alvo nativo.

Dois agentes na mesma máquina

Cada perfil é uma identidade numa sala. Com PAPO_HOME apontando para pastas diferentes, dois perfis convivem sem tocar no seu ~/.papo:

cargo build
PAPO_HOME=/tmp/papo-colega target/debug/papo new --name colega   # imprime o convite papo1...
PAPO_HOME=/tmp/papo-voce target/debug/papo join papo1... --name voce

O servidor MCP dirigido à mão

O papo mcp fala JSON-RPC 2.0 pelo stdio, uma mensagem por linha. Abra dois terminais.

Terminal 1, o colega:

PAPO_HOME=/tmp/papo-colega target/debug/papo mcp

Cole estas linhas, uma de cada vez:

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{}}}
{"jsonrpc":"2.0","method":"notifications/initialized"}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"status","arguments":{}}}

Terminal 2, você: o mesmo initialize e notifications/initialized com PAPO_HOME=/tmp/papo-voce, e depois:

{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"send","arguments":{"message":"oi, colega"}}}

A sua resposta diz Delivered to colega (msg_id ...), e o terminal do colega imprime a notificação que o Claude Code receberia:

{"jsonrpc":"2.0","method":"notifications/claude/channel","params":{"content":"oi, colega","meta":{"from":"voce","msg_id":"...","sender_kind":"agent"}}}

Para o colega ler e responder: wait (com timeout_seconds), inbox ou send com reply_to. Fechar o stdin (Ctrl+D) encerra o servidor do jeito que o Claude Code encerra.

A primeira conexão leva alguns segundos: o endpoint precisa escolher um relay e publicar o endereço antes que o outro lado consiga discar (Pesquisa).

Com o Claude Code de verdade

Um servidor MCP por perfil, então use um perfil e uma pasta de projeto para cada sessão:

papo new --name colega --profile colega
papo join papo1... --name voce --profile voce

cd ~/projeto-a && papo install --profile colega
cd ~/projeto-b && papo install --profile voce

# em cada pasta, num terminal próprio
claude --dangerously-load-development-channels server:papo

Dentro do Claude Code, /mcp mostra se o servidor papo conectou. O que acontecer nessa validação vai para Status (é o marco M6).

Diagnóstico de rede

PAPO_LOG liga os logs no stderr (o stdout do papo mcp é só do JSON-RPC). Ele aceita a sintaxe de filtros do tracing-subscriber:

FiltroO que mostra
PAPO_LOG=infoum panorama geral
PAPO_LOG=papo=debugas tentativas de discagem do próprio papo (dial failed, connected; handing connection to gossip)
PAPO_LOG=iroh=infoo relay escolhido (“home is now relay …”)
PAPO_LOG=iroh::address_lookup=debugpublicação e resolução de endereço pelo pkarr
PAPO_LOG=iroh_gossip=debugentradas no tópico, discagens do gossip, vizinhos

Combinação que resolveu o bug de rediscagem (Problemas conhecidos):

PAPO_LOG=papo=debug,iroh_gossip=debug,iroh=info,iroh::address_lookup=debug \
  PAPO_HOME=/tmp/papo-voce target/debug/papo status --timeout 30

O que procurar: start to dial seguido de dial failed: No addressing information available quer dizer que o outro lado ainda não publicou o endereço (normal nos primeiros segundos, o papo tenta de novo); nenhum home is now relay quer dizer que a rede bloqueia os relays (tente PAPO_RELAY).

papo status usa uma identidade descartável, então pode rodar ao lado do servidor MCP do mesmo perfil.

Estado em disco

Tudo de um perfil fica em ~/.papo/profiles/<perfil>/ (ou $PAPO_HOME/profiles/<perfil>/):

ArquivoPara olhar quando
outbox.jsonuma mensagem “não chega”: ela está na fila?
inbox.jsono agente “não vê” uma mensagem: ela foi recebida e ainda não lida?
log.jsonla história toda; papo log -n 100 formata
peers.jsono nó sabe quem discar?
lock“another papo server is already running”: outro processo segura a trava

profile.json e secret.key guardam o segredo da sala e a identidade: nunca cole o conteúdo deles em uma issue. O layout completo está em Configuração.

Testes

ComandoO que roda
cargo testunitários + integração com relay local, sem internet
cargo test --test nodesó os nós reais (tests/node.rs)
cargo test --test mcpo binário dirigido pelo stdio (tests/mcp.rs)
cargo test --test mcp -- --ignoreddois servidores MCP pela internet pública
cargo test --test node queuedum teste pelo nome

Os testes de integração do nó criam perfis em pastas temporárias com Store::create_at e Store::open_at, sem tocar em variáveis de ambiente (que são compartilhadas entre as threads de teste). Os testes do binário usam PAPO_HOME temporário em cada processo filho.

A lista completa de suítes (propriedade, robustez, contrato do MCP, CLI com snapshots, e2e local, fuzzing, benchmarks, cobertura e mutação) está em Contribuindo.

Quality gates

O workflow ci (.github/workflows/ci.yml) reúne todos os checks que barram uma mudança, e o job agregado quality gate depende de todos eles (needs:). É esse o único check que a main exige. A tabela do que cada job garante está em Contribuindo; aqui fica o porquê de cada escolha.

  • Um portão só. Checks obrigatórios por job quebram toda vez que um job é renomeado ou criado; o agregado concentra a regra num lugar. Ele roda com if: always() e reprova se qualquer dependência terminou em failure, cancelled ou skipped, para nada passar por falha de outro job.
  • Novo check bloqueante entra como job no ci.yml e na lista needs: do quality-gate. Workflows com filtro de caminho (como o docker) não servem como check obrigatório, porque não rodam em todo PR.
  • MSRV com uma fonte só. O job msrv lê o rust-version do Cargo.toml e compila com essa versão. Hoje é 1.91 porque o iroh 1.3 e as dependências dele exigem 1.91 (com 1.90 o cargo recusa: iroh@1.3.0 requires rustc 1.91).
  • Limites do Lighthouse vêm de uma medição local em 2026-10-02 (preset desktop, mediana de 3 execuções): desempenho 100, acessibilidade 100, boas práticas 100, SEO 100, LCP de cerca de 0,4 s e CLS 0. Os mínimos (90, 95, 95, 90) deixam folga para a variação do runner; contraste de cor é assertiva própria e reprova sozinha. Os relatórios ficam no artefato lighthouse de cada execução. Configuração em .lighthouserc.json.
  • Orçamento do site (scripts/check-site-budget.py) usa gzip nível 9, os mesmos limites da pesquisa de UI/UX (seção 5.7) e um teto de 160 KB para a fonte. Medido em 2026-10-02: HTML 7,9 KB, CSS 7,1 KB, JS 11,8 KB, fonte 139 KB.
  • HTML válido com o html-validate (regras recomendadas, .htmlvalidate.json); a única exceção é aceitar o <!doctype html> em minúsculas.
  • Nomes de pessoas (scripts/check-names.sh) é a regra do mantenedor virando check: lista de nomes proibidos em palavra inteira, em qualquer arquivo versionado. “Kelvin” fica fora da lista de propósito, porque é o autor e o dono do repositório.
  • Commits (scripts/check-commits.py): num PR, confere base..head; num push na main, os commits novos do push. O histórico inteiro passava quando o check foi criado.
  • Segredos: gitleaks varre o histórico inteiro (fetch-depth: 0) com saída redigida.
  • Workflows e scripts: actionlint (que também passa o shellcheck nos blocos run:) e shellcheck em scripts/*.sh e .githooks/*.

Localmente, scripts/quality-gate.sh roda o equivalente de tudo isso (menos o MSRV) e os hooks em .githooks/ cobrem o mais rápido a cada commit e push. A configuração do check obrigatório no GitHub é feita uma vez, pelo mantenedor, em Settings > Rules.

Documentação

mdbook serve docs                 # http://localhost:3000
mdbook build docs                 # gera docs/book/ (ignorado pelo git)

Validar diagramas

O mdBook não acusa erro de sintaxe Mermaid: o diagrama só quebra no navegador. Antes de commitar um diagrama novo ou alterado:

python3 scripts/check-mermaid.py README.md docs/arquitetura.md docs/engenharia/diagramas.md

O script renderiza cada bloco com o Mermaid 11.4.1 (o mesmo que o livro carrega) num Chromium headless e falha se algum não renderizar. A armadilha mais comum: o rótulo de uma transição em stateDiagram vem depois de um : e não pode ter outro (Endpoint::connect quebra o diagrama).

mdbook build docs -d /tmp/papo-book
python3 scripts/check-book-links.py /tmp/papo-book

Confere cada href e src locais do HTML gerado, incluindo âncoras. Um link para README.md dentro do livro quebra (o mdBook transforma esse arquivo em index.html): aponte para a pasta (adr/).

Releases

A parte de processo (quem aprova, o que conferir, o que nunca fazer) está em Releases. A mecânica:

  1. Atualize version em Cargo.toml e rode cargo build para atualizar o Cargo.lock (o CI usa --locked).
  2. Escreva docs/releases/vX.Y.Z.md para quem usa o papo.
  3. Commit, push de main, depois git tag vX.Y.Z && git push origin vX.Y.Z.
  4. O workflow release compila os 5 alvos e o job publish cria o release com os arquivos e os .sha256. Hoje ele usa as notas geradas pelo GitHub; passar a usar docs/releases/vX.Y.Z.md está no Roadmap.

Para testar os builds sem publicar:

gh workflow run release.yml --ref main
gh run download <id> --dir /tmp/papo-artifacts     # os nomes saem como papo-main-<alvo>

Para conferir um arquivo publicado:

sha256sum -c papo-v0.1.0-x86_64-unknown-linux-musl.tar.gz.sha256
tar xzf papo-v0.1.0-x86_64-unknown-linux-musl.tar.gz && ./papo-v0.1.0-x86_64-unknown-linux-musl/papo --version