AUR-424 — Extrator de documentação Go sem dependência externa
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
Comando
AURUMCODE_SOURCE_DIR=<diretório do projeto Go> \
AURUMCODE_OUTPUT_DIR=<diretório de saída> \
go run ./cmd/regenerate-docs
Internamente, o comando acima delega a extração de páginas Go para
internal/documentation/extractors/go.GoExtractor, através da chamada de
registro já existente em cmd/regenerate-docs/main.go
(registerLanguageExtractors):
if err := register(goExtractor.NewGoExtractor(runner)); err != nil {
return err
}
Este card reescreve inteiramente internal/documentation/extractors/go; a
assinatura NewGoExtractor(runner) é preservada de propósito para não exigir
nenhuma mudança nesse call site.
O que mudou
Antes deste card, GoExtractor gerava documentação chamando o binário externo
gomarkdoc via site.CommandRunner (runner.Run(ctx, "gomarkdoc", ...)).
gomarkdoc não existe no sandbox offline (rede negada, sem instalação). Uma
falha de exec.LookPath virava extractors.ToolUnavailableError, que o
pipeline trata como "pular esta linguagem" — não como falha. Um projeto Go
inteiro saía com zero páginas e exit 0, indistinguível de um projeto sem nada
para documentar.
A partir deste card, GoExtractor usa somente a standard library:
go/build.Context.MatchFileseleciona quais arquivos.godo diretório pertencem ao pacote documentado (arquivos*_test.gosão ignorados);go/parser.ParseFilelê cada arquivo selecionado;go/doc.NewFromFilesassocia comentários de pacote, tipo, função e método às suas declarações — exatamente comogo doc/pkg.go.dev fazem;go/printer.Fprintrenderiza a assinatura de cada declaração (sem corpo de função, sem comentário duplicado).
Alvo de build fixado (determinismo)
O filtro de build constraints não é um detalhe: sem ele, um diretório com
arquivos mutuamente exclusivos de plataforma (impl_linux.go e
impl_windows.go, ou dois arquivos com linhas //go:build opostas) tem
todos eles reunidos em um único pacote, e a página resultante anuncia uma
superfície de API que nenhum build daquele pacote jamais contém. Isso reprova
diretamente a cláusula do AC-001 "repetir a execução sobre a mesma entrada
produz a mesma saída" no sentido que importa: a saída deixa de descrever uma
configuração real.
Por isso documentBuildContext() fixa o alvo em constantes, e não em
runtime.GOOS/runtime.GOARCH:
| Campo | Valor | Razão |
|---|---|---|
GOOS |
linux |
Alvo fixo: a página não pode depender da máquina que a gerou. |
GOARCH |
amd64 |
idem. |
CgoEnabled |
false |
Caso contrário CGO_ENABLED do host decidiria o conteúdo. |
BuildTags |
vazio | Caso contrário -tags do host decidiria o conteúdo. |
Consequência declarada: um arquivo *_windows.go, *_darwin.go ou guardado
por //go:build windows não é documentado. Um diretório cujos únicos
arquivos Go são excluídos pelo alvo fixado não é um pacote para efeito deste
extrator, e não gera página nem erro.
Nome de arquivo de saída injetivo
O caminho do pacote (relativo à raiz do fonte) é achatado em um único nome de
arquivo por outputBaseName. O esquema anterior trocava todo separador por
_ e parava aí, o que não é injetivo: os diretórios a/b e a_b viravam
ambos a_b.md, e o pacote escrito por último apagava a página do primeiro sem
erro algum em ExtractResult.
O esquema atual reserva _ como introdutor de escape, de modo que todo _ na
saída é o primeiro byte de uma sequência de dois:
| Sequência | Significado |
|---|---|
_u |
um _ literal dentro de um segmento de caminho |
__ |
um separador de caminho |
Como u ≠ _, a decodificação é determinística da esquerda para a direita,
logo a codificação é injetiva. Nomes sem separador e sem _ — o caso comum,
ledger — passam intactos. O pacote na raiz do fonte não tem segmento algum e
recebe o nome root; o único caminho real que colidiria com ele (um diretório
de topo chamado root) é deslocado para root__, um segmento final vazio que
filepath.Rel nunca produz.
Nenhum processo externo é iniciado; nenhuma chamada de rede é feita.
GoExtractor.Validate agora sempre retorna nil: não há mais nada externo
para verificar.
O tipo do parâmetro runner deixou de ser site.CommandRunner e passou a ser
uma interface local mínima (commandRunner, com o mesmo único método Run).
A satisfação de interface em Go é estrutural, então o site.CommandRunner que
main.go já passa continua sendo aceito sem nenhuma mudança nesse arquivo;
o pacote internal/documentation/extractors/go deixou de importar
internal/documentation/site por completo, porque não tem mais nada para
mandar um runner executar.
Opções (via variáveis de ambiente lidas por cmd/regenerate-docs)
| Variável | Efeito |
|---|---|
AURUMCODE_SOURCE_DIR |
Raiz do projeto a documentar. |
AURUMCODE_OUTPUT_DIR |
Diretório onde as páginas Markdown são escritas. |
AURUMCODE_INCREMENTAL |
Quando true, pula pacotes cujo .md já é mais novo que o fonte. |
GoExtractor.WithIncrementalMode expõe o mesmo controle programaticamente.
Exit codes
Estes são os exit codes do próprio GoExtractor.Extract (biblioteca) e do
aceite deste card (tests/acceptance/AUR-424.sh), não os do binário completo
cmd/regenerate-docs (que soma o resultado de todas as linguagens):
Extractretornaerror != nilapenas para uma entrada inválida (idioma errado, diretório de origem inexistente, diretório de saída não gravável) ou cancelamento de contexto; um pacote individual que falhar ao parsear é reportado emExtractResult.Errorse não interrompe os demais.tests/acceptance/AUR-424.sh: ver "Exit codes do aceite" mais abaixo.
Exemplo executável (sem segredo)
Com um projeto Go mínimo em ./tests/fixtures/docs/goproject (fixture deste
card: um pacote com um tipo exportado, dois métodos/funções exportados e
comentários de documentação reais):
$ AURUMCODE_SOURCE_DIR=./tests/fixtures/docs/goproject AURUMCODE_OUTPUT_DIR=./site \
go run ./cmd/regenerate-docs
...
✓ Source: .../tests/fixtures/docs/goproject | Output: ./site | ...
📊 Generated 1 markdown file(s) in ./site:
- site/go/root.md
aurumcode: result=ok docs=1 skipped=0 failed=0 languages_skipped=none ...
site/go/root.md contém o comentário de pacote, type Greeting struct { ... }
com seus campos e métodos (NewGreeting, String), e func Add/func Max
com seus comentários de doc — símbolos e prosa reais extraídos do fonte, não
um placeholder.
Nenhuma variável de ambiente com segredo é lida por este caminho de código.
Diagnóstico de falha
skipped: go: .../ zero páginas Go com exit 0 (comportamento pré-correção): sintoma exato do defeito que este card fecha — dependência degomarkdocausente do sandbox. Reproduzido durante o TDD deste card restaurando temporariamente a implementação antiga (ver## Prova).no documentable Go package found in <dir>: o diretório tem apenas arquivos_test.go, ou nenhum arquivo.go, ou todos os pacotes encontrados são pacotes de teste externo (<pkg>_test).- Um símbolo existe no fonte mas não aparece na página: o arquivo que o
declara é excluído pelo alvo de build fixado (
linux/amd64, sem cgo, sem tags) — ver "Alvo de build fixado" acima. - Página gerada vazia ou ausente:
extractors.ConfirmOutputFilerecusa contar como gerado um arquivo que não existe, é diretório, ou tem zero bytes — o mesmo padrão de todo extrator deste diretório.
Nota de contrato: go run ./cmd/regenerate-docs dentro do sandbox de aceite
Na integração deste card, dois gaps medidos no candidato original foram
reparados antes da revisão, e o card está done com essa correção
incorporada:
paths/read_pathsforam ampliados para cobrir o fecho transitivo real decmd/regenerate-docs/main.go—internal/pipeline,internal/llme seus provedores, os extratores irmãos,internal/qa/browserproof(importado portests/e2e/browser_chain_test.go),action.ymle.aurumcode, além dos cinco arquivos da tabela "Contratos pré-existentes" acima.- A lane de regressão (
internal/documentation/...,internal/pipeline/...,cmd/...,tests/e2e/...,tests/characterization/...) passou a rodar de fato dentro do accept selado, em vez de ficar fora do alcance do próprio aceite.
Hoje, dentro do container bootstrap-readonly-v1 (rede negada, apenas os
arquivos allowlisted), go run ./cmd/regenerate-docs compila, e a lane E2E de
tests/acceptance/AUR-424.sh executa o comando declarado de verdade — não
mais um substituto — contra a fixture real deste card, com a lane de
regressão incluída. O accept selado sai 0.
As lanes Unit e Integration continuam provando o comportamento deste card
através do mesmo mecanismo de registro que main.go usa (extractors.Registry
real, com o GoExtractor real, resolvido por linguagem), contra a fixture
real deste card.
Fora do sandbox de aceite (checkout completo, com toda a árvore do módulo
presente), o exemplo acima roda o mesmo binário completo e o aceite também
sai 0 — o mesmo resultado dentro e fora do sandbox, agora que o gap de
paths/read_paths foi fechado.
Escopo desta correção: somente Go
Este card fecha a dependência de ferramenta externa do extrator Go e de mais
nenhum. Os outros sete extratores deste diretório — python
(pydoc-markdown), javascript (typedoc), csharp (dotnet + xmldocmd),
cpp (doxygen), rust (cargo), bash e powershell (pwsh) — continuam
invocando binários externos e continuam sujeitos ao mesmo skip silencioso
quando esses binários faltam. Nada aqui deve ser lido como "a classe de defeito
foi fechada": ela foi fechada para uma linguagem.
Isso está refletido nos testes: as tabelas compartilhadas de
tool_failure_test.go e tool_unavailable_test.go perderam apenas a linha
do Go e mantêm intactas as asserções das outras sete linguagens.
Contratos pré-existentes que este card mudou de propósito
A correção muda um contrato que testes fora de paths deste card afirmavam.
Cada um foi atualizado explicitamente, e não afrouxado:
| Arquivo | Antes | Agora |
|---|---|---|
internal/documentation/extractors/tool_unavailable_test.go |
linha go/gomarkdoc exigia ToolUnavailableError |
linha removida; TestGoExtractorValidate_HasNoExternalTool exige que nenhuma falha de runner torne o Go indisponível |
internal/documentation/extractors/tool_failure_test.go |
linha go/gomarkdoc em realToolCases() |
linha removida; TestGoExtractorValidate_RealPATHIsIrrelevant exige sucesso com PATH vazio e com gomarkdoc quebrado no PATH |
internal/documentation/extractors/output_confirmed_test.go |
linha go com toolWritesOutput: true |
toolWritesOutput: false — o Go passou a escrever a própria saída; a invariante "nada reportado sem prova em disco" continua rodando nessa linha |
tests/characterization/legacy/documentation/characterization.go + classification.tsv |
probeGo fixava "chama o runner exatamente 1 vez" |
recaracterizado: zero chamadas externas e página confirmada em disco com o símbolo real |
tests/e2e/smoke_test.go |
TestSmokeMissingToolchainFailsLoudly usava Go como linguagem sem ferramenta |
a fixture da caso troca Go por Python (que ainda depende de pydoc-markdown), preservando o piso "sem ferramenta nenhuma, o run falha alto"; TestSmokeGoDocumentsWithoutAnyToolOnPATH foi acrescentado para o fato novo |
O piso de degradação não foi removido: ele foi mantido em uma linguagem que ainda o exerce.
Pendências nomeadas (não implementadas por este card)
- Os cabeçalhos gerados (
## Types,## Functions,## Constants,## Variables) não constam dostructuralHeadingsdo scaffold do site. go/printer.Fprintemite tabulações; a saída não passa porgofmt, então os blocos de código podem carregar tabs em vez da formatação canônica.- A guarda
no-test-filesdetests/acceptance/AUR-424.shusagrep -Fq '\[no test files\]', cujo padrão literal (com as barras invertidas) nunca casa — a guarda está morta. selectPackagedescarta em silêncio um segundo pacote não-teste no mesmo diretório, em vez de reportar a ambiguidade.- O
context.Contextnão é propagado para dentro deextractPackage: o cancelamento só é observado entre pacotes, não durante o parse de um. hasGoFilespassou a propagar o erro dematchedGoFiles, mas ofilepath.WalkdefindGoPackagesdescarta erros (return nil // Skip errors): um arquivo ilegível faz o diretório inteiro desaparecer em silêncio — mais uma instância da classe de skip silencioso que este card fecha para o caso dogomarkdoc, ainda aberta para o caso de I/O.
Limitações de infraestrutura medidas
./internal/... inteiro não é utilizável como lane de regressão
go test ./internal/... falha com internal/evidence [setup failed]: o teste
desse pacote importa github.com/Mpaape/AurumCode/tests/integration, que
contém arquivos package main (tests/integration/AUR-001.go, AUR-002.go,
AUR-004.go) misturados com arquivos package integration, então o pacote não
compila como importável. Isso é pré-existente e alheio ao AUR-424:
reproduz-se identicamente em ec0495c, o commit pai deste candidato. Por isso
a lane de regressão do aceite usa uma lista explícita de pacotes —
./internal/documentation/..., ./internal/pipeline/..., ./cmd/...,
./tests/e2e/... e ./tests/characterization/... — que é onde vivem todos os
contratos que este card mudou (incluindo a classificação de skip em
internal/pipeline, cujo significado mudou para o Go). tests/unit e
tests/integration ficam de fora pelo mesmo motivo que internal/evidence:
misturam package main com o pacote do diretório e não compilam como um todo;
os seletores próprios do card já os cobrem via as lanes Unit e Integration. Fechar
internal/evidence é trabalho de outro card.
Gap de paths/read_paths reparado na integração
Os cinco arquivos da tabela "Contratos pré-existentes" acima, que estavam
fora de paths no candidato original, e o fecho de imports que a lane de
regressão e o E2E real precisam (internal/pipeline, internal/llm,
internal/qa/browserproof, action.yml, .aurumcode) foram incorporados a
paths/read_paths pelo coordenador antes da revisão — ver "Nota de
contrato" acima.
Com o gap fechado, tests/acceptance/AUR-424.sh sai 0 dentro de
bootstrap-readonly-v1, com a lane de regressão executada de verdade e o E2E
rodando o comando declarado real. A garantia que este card fecha permanece
válida: um aceite verde continua exigindo que a lane realmente rode — ela
roda, e não apenas está presente por acaso. Em checkout completo (árvore
inteira presente), o mesmo script também sai 0, o mesmo resultado dentro
e fora do sandbox.
Exit codes do aceite
0prova, com a lane de regressão executada;1comportamento ausente (AUR-424/AC-001/behavior-missing), regressão fora do diff (AUR-424/AC-001/regression:<rc>), saída não determinística (AUR-424/AC-001/nondeterministic-output:...) ou dependência externa reintroduzida (AUR-424/AC-001/MUT-001/...);64seletor desconhecido;69infraestrutura (goausente, module cache ausente, árvores não materializadas).
Prova
- Unit
tests/unit/AUR-424.go::TestAUR424: fixture em memória, zero chamadas ao runner, página gerada com símbolo e comentário reais,Validatesemprenil. - Integration
tests/integration/AUR-424.go::IntegrationAUR424: registro real emextractors.Registry, extração da fixture rastreadatests/fixtures/docs/goprojectduas vezes com digest idêntico (determinismo), e verificação textual do call site emcmd/regenerate-docs/main.go. - E2E
tests/e2e/AUR-424.sh::E2EAUR424: execução real, não mais checagem estática de bytes. Roda o comando declarado (go run ./cmd/regenerate-docs) duas vezes sobre a fixture e compara os digests (cláusula de repetição do AC-001), verifica que a página gerada carrega os símbolos e comentários reais da fixture, e roda o binário compilado uma terceira vez comPATHvazio — o falsificador comportamental do MUT-001: um extrator que volte a chamargomarkdocnão acha nada para chamar, reporta a linguagem como pulada e não escreve página. - Pacote
internal/documentation/extractors/go:TestGoExtractor_BuildConstraintsPickOneTarget(a página traz o símbolo do alvo fixado e nenhum símbolo de outra plataforma, e duas execuções coincidem),TestGoExtractor_PlatformOnlyDirectoryIsNotAPackage,TestOutputBaseName_IsInjectiveeTestGoExtractor_CollidingPackageNamesBothSurvive. - Acceptance
tests/acceptance/AUR-424.sh::AC-001via./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-424, agora incluindo a lane de regressão descrita acima.
Todo comando Go destes scripts roda com teto de memória embutido no próprio
script (ulimit -v 8388608, GOMEMLIMIT=2GiB, go test -timeout 300s), para
que um revisor que rode bash tests/acceptance/AUR-424.sh diretamente tenha a
mesma proteção.