Arquitetura do núcleo do BeeverForge
Princípios de engenharia
Os princípios do núcleo estão escritos no seu documento de visão e nos seus registros de decisão. Esta página os lista com o código que faz cumprir cada um e indica onde o código fica aquém do enunciado.
Princípios da visão#
De docs/core/00-vision.md. São objetivos declarados; quando o código não cumpre um deles por completo, o item diz até onde vai.
- O núcleo não nomeia nenhum fornecedor. Um fornecedor é alcançado por uma porta, escolhida por um registro, a partir da configuração. O teste que garante isso cobre só
beeverforge/contractsebeeverforge/policy; as linhas de adaptadores incluídos nomeiam seus fornecedores, e o destino de métricas é inferido comodynamodbquandoBEEVERFORGE_METRICS_TABLEestá definida. - Tudo o que está fora do núcleo é substituível, por alguém que não sejam os mantenedores: um ponto de entrada, não um pull request. O motor é a exceção: o Temporal não está atrás de uma porta.
- Uma aplicação construída sobre o núcleo não é o núcleo. Ela usa a mesma superfície que a aplicação de um terceiro usaria.
- O núcleo é útil por si só. O documento acrescenta que nada do que ele inclui é retirado para fabricar um caminho de upgrade; é um compromisso declarado, que o código não pode demonstrar.
- Um contrato é derivado do que o núcleo chama, nunca do que um provedor oferece.
- Um eixo é agnóstico quando nasce com duas implementações, não quando declara uma interface (ADR-0022). Vários eixos ainda não o cumprem; Modelo de extensão os lista.
Decisões do ADR-0001#
O ADR-0001 fixa o modelo conceitual. Suas decisões, por título:
- D-1 O framework e o projeto são separados por um manifesto declarativo.
- D-2 O conhecimento do projeto é uma cascata de autoridade: restringir, nunca afrouxar.
- D-3 O framework é dono dos espaços; a conformidade os faz cumprir.
- D-4 Um ambiente efêmero com um cache de dependências persistente.
- D-5 Um worker genérico por ticket; executor e revisor são contextos separados.
- D-6 O rótulo é um palpite; o diff é a verdade. Os componentes resolvem os stacks.
- D-7 As dependências declaradas são a verdade; as inferidas são indicativas.
- D-8 Um controle de qualidade da especificação: confiar para começar, verificar para terminar.
- D-9 A documentação tem papéis padrão, não locais padrão; os ADRs são a constituição.
- D-10 Derivar, não manter.
- D-11 Determinístico primeiro.
- D-12 A plataforma é um operador do ciclo de vida, não um simples abridor de pull requests.
- D-13 Os avaliadores são chamadas estruturadas sobre controles determinísticos; o comportamento do agente é medido, não presumido.
- D-14 Um único tenant, vários projetos.
- D-15 Recolhimento, responsabilidade e tratamento de impedimentos.
- D-16 Temporal como motor de execução durável.
- D-17 Uma caixa de tarefas remota que executa o job inteiro. Neste repositório essa caixa é apenas uma descrição; seu executor é um complemento.
Invariantes, verificados contra o código#
- Nunca um travamento silencioso
- Toda espera no workflow do job tem um prazo ou uma pessoa no fim: o acompanhamento do merge, a aprovação de produção e um impedimento sem resposta têm prazos, e uma espera por decisão se mantém até ser respondida. As esperas trazem as opções que uma pessoa pode escolher. Um pull request em que nenhum check informa nada é sinalizado, não interpretado como verde.
- Nada se perde, nada se duplica
- Os jobs são workflows duráveis. Os pull requests são buscar e depois atualizar; antes de reconstruir um branch, o executor pergunta à plataforma Git se há um pull request, e se não consegue ler a resposta ele para em vez de adivinhar.
- A vaga é liberada no merge
- O workflow do job mantém sua vaga até o merge do pull request, até o fim da promoção quando ela está habilitada, ou até o job ser pulado. Um job em espera mantém sua vaga: um impedimento simples é pulado após 3 dias, e uma espera por decisão aguarda até ser respondida.
- A qualidade é um piso
- Os papéis
testesecuritysão obrigatórios antes da passada de implementação. O piso verifica que os papéis existem, não o que executam, e o controlesecurityherdado é não bloqueante. - O código é a fonte da verdade
- O mapa de conhecimento é entregue a um agente só quando suas somas de verificação provam que ele descreve a cópia de trabalho do próprio job; caso contrário, nada é entregue.
- Atribuído ao bot
- A fábrica faz commits e age com a identidade de bot definida pela implantação (
BEEVERFORGE_BOT_NAME,BEEVERFORGE_BOT_EMAIL). - Privilégio mínimo
- Em parte. O campo
permissionsdo manifesto é aceito e marcado como inerte: nenhum código o lê, e a restrição de permissões por projeto não está implementada. O que limita uma execução é o isolamento da caixa e seubox.env.
Uma camada de ações para todos os transportes#
Tudo o que uma pessoa pode pedir à fábrica está escrito uma única vez, no catálogo de ações (ADR-0039): beeverforge/actions/, executado por actions.perform. Quem pediu é um parâmetro da chamada, não uma propriedade do transporte.
- A API do painel serve o catálogo em
POST /api/act/{name}, e suas rotas nomeadas chamam a mesma função. - A linha de comando o serve como
beeverforge act, e vários de seus outros comandos, como adicionar ou remover um projeto e pausar o recolhimento, chamam a mesma função. - À conversa do tech-lead só é oferecido o que
actions.proposable()retorna para a credencial de quem pergunta.
Um cartão tem um ciclo de vida e uma porta#
O estado de um cartão só muda por lifecycle.card.transition() (ADR-0055). A porta lê o registro do cartão e responde a um evento repetido com a linha já registrada; pergunta à tabela pura se o evento é permitido a partir de onde o cartão está; executa a parte do motor da decisão de uma pessoa, se houver; registra a transição no próximo número de sequência, a menos que outro escritor o tenha tomado; e depois aplica o que a tabela diz que vem em seguida.
- As tabelas de
lifecycle/table.pysão puras: sem relógio, sem armazenamento, sem tracker. Os estados do cartão sãobacklog,todo,running,waiting_on_a_person,merged,staged,delivered,closederemoved. - Um teste de guarda não permite nenhum escritor de cartões fora da porta; a única exceção, por regra, é a caixa escrevendo suas marcas de progresso.
- O ADR-0055 está implementado, embora seu registro ainda diga Proposed. Um evento,
picked_up, continua indefinido e é recusado a partir de qualquer estado. - Os jobs não têm uma tabela equivalente. As transições dos jobs são decididas no executor e no workflow, que é a parte do ADR-0033 que continua proposta.
As recusas nomeiam a causa e a solução#
Uma recusa diz o que falhou e o que fazer. Na linha de comando, cli_refusals.py associa um erro do provedor a uma causa conhecida e imprime uma frase com a causa e sua solução, seguida das palavras do próprio provedor. Um erro que ele não reconhece mantém as palavras do provedor e nenhuma solução é inventada. Os testes exigem de cada causa sua própria solução e impedem que defeitos do próprio núcleo sejam apresentados como do provedor.
“Não foi possível ver” não é “não há nada”#
Toda leitura de um tracker segue uma regra, enunciada em sua classe base: None significa que a leitura falhou; uma lista vazia significa que a leitura deu certo e não encontrou nada. Os quadros seguem a mesma regra, e a suíte de conformidade de adaptadores a verifica para os trackers. Valores tipados como Unreadable mantêm a mesma distinção em outros pontos.
Recusar antes de escrever#
Uma verificação que pode recusar roda antes de qualquer escrita. Uma transição de cartão recusada não muda nada e diz por quê; o piso recusa um job antes da passada de implementação; o pipeline de conhecimento recusa um destino fora do seu diretório antes de clonar qualquer coisa; uma opção de linha de comando recusada informa que nada foi lido e nada foi escrito.
Um orçamento de prompt que nomeia o que deixa de fora#
O prompt de um job tem um único orçamento (ADR-0056): BEEVERFORGE_PROMPT_BUDGET, padrão de 160.000 bytes, compartilhado pelo cartão, pelo plano e pelos documentos que o projeto declara. off o desativa; um valor que não pode ser lido é recusado. Cada documento deixado de fora é nomeado no prompt, pelo caminho que o agente pode abrir e pelo tamanho, sob um título que diz ao agente para abri-lo antes de mudar o que ele rege. Um documento cortado pelo seu próprio limite de tamanho é listado com quanto dele foi incluído.
Neutralidade de fornecedores, garantida por testes#
tests/test_kernel_names_no_vendor.pyanalisabeeverforge/contractsebeeverforge/policye falha diante de qualquer nome de fornecedor.tests/test_the_core_names_no_vendor.pyfalha se um fornecedor de chat é nomeado fora dos comentários do núcleo.tests/test_the_core_addon_ledger.pyderiva cada importação de um SDK de fornecedor e mantém a lista igual ao registro emdocs/core/07-extensibility.md.tests/test_the_core_is_a_core.pycarrega o núcleo em um subprocesso onde todo SDK de fornecedor se recusa a ser importado, e verifica que o catálogo de ações é resolvido e que o painel inicia.
A camada de conhecimento#
A camada de conhecimento é um mapa de módulos de cada repositório, gerado de forma determinística a partir da estrutura de diretórios, das árvores sintáticas do Python e da busca de padrões em fontes TypeScript, JavaScript e C#, sem chamadas a um LLM. O mesmo estado do repositório produz uma saída idêntica byte a byte.
- Ela vem ativada por padrão (
knowledge_map: true; ADR-0035) e pode ser desativada por projeto. - Ela é publicada no repositório de contexto do projeto em
.okf/repos/<owner>--<name>/, nunca no repositório em que se trabalha. Publicar exigeproduct.docs_repo; sem isso, a atualização não faz nada. - Um agente recebe o mapa só quando se prova que ele está atualizado em relação à sua própria cópia de trabalho. Ele acelera encontrar código e não substitui lê-lo.
O papel de produto#
O papel de produto é opcional. Ele é habilitado por uma seção product: na entrada do projeto no registro, que precisa nomear o repositório de documentação que guarda os requisitos do produto (ADR-0019).
Fontes#
docs/core/00-vision.mddocs/architecture.mdbeeverforge/actions/__init__.pybeeverforge/api/app.pybeeverforge/lifecycle/card.pybeeverforge/lifecycle/table.pybeeverforge/cli_refusals.pybeeverforge/adapters/tracker/base.pybeeverforge/loader.pybeeverforge/credentials.pybeeverforge/orchestrator/context.pybeeverforge/adapters/agent/base.pybeeverforge/knowledge/generator.pybeeverforge/knowledge/pipeline.pybeeverforge/contracts/product.pytests/test_the_card_lifecycle_has_one_door.pytests/test_kernel_names_no_vendor.pytests/test_the_core_is_a_core.py- ADR-0001, ADR-0019, ADR-0033, ADR-0035, ADR-0039, ADR-0055, ADR-0056