Configurando o Handbooks
Cinco camadas de precedência, um registro e um comando que diz exatamente qual camada venceu.
Cada configuração é declarada uma única vez, em uma única tabela de registro. As flags
da CLI, os nomes das variáveis de ambiente, as chaves do arquivo de configuração, o
.env.example, o handbook.config.example.yaml e a
referência de configuração são todos gerados a partir
dela — assim não podem se descolar uns dos outros, e um teste de deriva falha o build se
alguém tentar.
Precedência, da mais alta para a mais baixa
- Flag da CLI —
--read-workers 4 - Ambiente do shell —
HANDBOOK_GENERATE_READ_WORKERS, depoisHANDBOOK_READ_WORKERS, depois um alias de fornecedor comoOPENAI_MODEL - A cascata de
.env— mesclada no ambiente antes de qualquer coisa lê-la handbook.config.yaml— descoberto subindo a partir do cwd, parando na raiz do git- Padrão do registro
A primeira camada que fornece um valor vence; todas as camadas abaixo são ignoradas para aquela configuração.
handbook.config.yaml
Coloque-o na raiz do repositório e faça commit dele. A descoberta sobe a partir do diretório de trabalho e para na fronteira do repositório — assim um projeto sem arquivo de configuração não herda o do diretório pai.
# Paths are resolved relative to THIS FILE, so the config stays portable.
source: ./src
work: ./.handbook
llm:
model: gpt-4o-mini
baseUrl: https://api.openai.com/v1
maxTokens: 16000
concurrency: 16
generate:
detail: deep
synthMode: doctor
narrateLang: en
readWorkers: 12
render:
title: Payments API Handbook
html: true
agentSite: true
llmsTxt: true
sourceBaseUrl: https://github.com/me/api/blob/main
studio:
port: 4860Duas coisas que você precisa saber:
- Aninhar e achatar são a mesma coisa.
generate: { detail: deep }e umgenerateDetail: deepplano significam exatamente o mesmo, porque o arquivo é achatado por junção em camelCase antes de ser lido. - Valores de
pathrelativos são resolvidos em relação ao diretório do próprio arquivo de configuração, não ao cwd. É isso que mantém um arquivo de configuração commitado funcionando não importa de onde você execute o comando.
Segredos são rejeitados aqui
llmApiKey / OPENAI_API_KEY e llmExtraBody / OPENAI_EXTRA_BODY nunca devem aparecer em um arquivo de
configuração — arquivos de configuração acabam commitados. O carregador recusa o arquivo de imediato e diz
por quê. Coloque os dois no .env ou no ambiente do shell. O baseUrl pode ser commitado sem problema, a
menos que a própria URL carregue credenciais (https://user:pass@host/v1), o que é recusado pelo mesmo
motivo.
Copie o handbook.config.example.yaml para começar; ele é gerado a partir do registro,
então lista todas as chaves que realmente existem.
Escopo por comando
Qualquer configuração pode ter o escopo restrito a um subcomando, nas três superfícies, com a mesma transformação:
export HANDBOOK_NARRATE_LANG=en # everywhere
export HANDBOOK_GENERATE_NARRATE_LANG=zh # …except generatenarrateLang: en
generate:
narrateLang: zhA forma com escopo sempre vence a forma plana.
Múltiplos ambientes
handbook generate --env prod --source ~/code/api --work work/api--env prod (ou HANDBOOK_ENV=prod) faz duas coisas:
- Carrega
.env.prod.local→.env.prod→.env.local→.env; o primeiro a escrever vence. - Prefere
handbook.config.prod.yamlao arquivo simples — em todos os diretórios visitados no caminho para cima, então um arquivo nomeado vence um simples mesmo que o simples esteja mais perto.
Um layout típico:
.env # team baseline, committed
.env.local # your machine, gitignored
.env.prod # team prod settings, committed (no secrets)
.env.prod.local # your prod credentials, gitignored
handbook.config.yaml # defaults
handbook.config.prod.yaml--env-file <path> ignora a cascata por completo e carrega exatamente aquele arquivo.
Um arquivo ausente ali é um erro explícito, não um fallback — você pediu um arquivo
específico.
Pergunte o que de fato foi resolvido
handbook config --command generateenvironment prod (--env)
env files .env.prod, .env
config file /repo/handbook.config.prod.yaml
SETTING VALUE FROM
source /Users/me/code/api flag --source
llmModel gpt-4o env HANDBOOK_LLM_MODEL
llmApiKey sk-•••••• env OPENAI_API_KEY
readWorkers 4 handbook.config.prod.yaml (generate.readWorkers)
detail deep handbook.config.prod.yaml (generate.detail)
narrateLang en defaulthandbook config --json # machine-readable
handbook config --check # exit 2 on the first invalid or missing valueColoque --check no CI
Uma variável com erro de digitação antes significava "rodou silenciosamente com o padrão". --check
transforma isso em uma falha com a variável nomeada na mensagem — muito mais barato do que descobrir o
problema quarenta minutos dentro de uma execução de geração.
config usa deliberadamente o resolvedor que não lança exceções: seu trabalho é
mostrar a configuração, inclusive quando ela está quebrada. Um --source ausente
aparece como uma linha visível — unset (required) em vez de derrubar justamente a
ferramenta que você usaria para depurar esse exato problema.
O que o resolvedor garante
-
Um valor vazio é lido como não definido.
HANDBOOK_TITLE=não pode produzir um handbook sem título. -
Um valor fornecido porém inválido nunca cai para um padrão. Um número com erro de digitação é um erro, não um 12 silencioso.
-
Os tipos são verificados na fronteira, com a origem nomeada na mensagem:
env HANDBOOK_READ_WORKERS: readWorkers must be an integer >= 1, got "twelve" handbook.config.yaml (generate.detail): detail must be one of brief | deep, got "verbose" -
A obrigatoriedade é verificada depois de cada camada, e o erro lista todas as formas de fornecer o valor:
source is required: pass --source, set HANDBOOK_GENERATE_SOURCE, or add it to handbook.config.yaml