Pular para conteúdo

AUR-427 — Documentação nativa de Rust e C#, sem ferramenta 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 Rust ou C#> \
AURUMCODE_OUTPUT_DIR=<diretório de saída> \
go run ./cmd/regenerate-docs

Internamente, o comando acima registra os dois novos extratores nativos por padrão, através da chamada de registro já existente em cmd/regenerate-docs/main.go (registerLanguageExtractors):

if !repoCodeOptIn["rust"] {
    if err := register(rustExtractor.NewNativeExtractor()); err != nil {
        return err
    }
}
if !repoCodeOptIn["csharp"] {
    if err := register(csharpExtractor.NewNativeExtractor()); err != nil {
        return err
    }
}

Nota de contrato: aurumcode docs --source ./projeto-rust não é alcançável a partir deste card

O AC-001 "When" (.board/cards/ready/AUR-427.md:73) e a seção "Public contract" (.board/cards/ready/AUR-427.md:82) declaram o comando aurumcode docs --source ./projeto-rust — a sintaxe do subcomando aurumcode docs que o card AUR-426 introduziu em cmd/aurumcode/docs.go.

Medido e reproduzível: este card não pode provar esse comando. cmd/aurumcode não está em paths nem em read_paths (.board/cards/ready/AUR-427.md:12), então nada neste candidato pode escrever em cmd/aurumcode/docs.go, e dentro do sandbox de aceite (bootstrap-readonly-v1, que materializa somente paths/read_paths) o diretório cmd/aurumcode sequer existe. cmd/aurumcode/docs.go mantém sua própria lista fixa de extratores em registerDocsExtractors (cmd/aurumcode/docs.go:280), documentada como deliberadamente sem Rust/C#:

"Rust and C# are deliberately not registered here... wiring that opt-in path a second time is outside what this card's --help promises."

Não há ponto de registro compartilhado entre cmd/regenerate-docs e cmd/aurumcode que este card possa tocar. Consequência declarada sem meias-palavras: um revisor que rode literalmente aurumcode docs --source <projeto-rust> hoje recebe zero páginas Rust — o subcomando aurumcode docs continua sem conhecer os extratores nativos deste card.

O Outcome, a Restrição medida, a Decisão registrada, os Non-goals e o MUT-001 do próprio card, porém, são inteiramente satisfazíveis dentro de paths: todos descrevem o par extrator+registro, não uma sintaxe de CLI específica. Este card entrega e prova o comportamento através do comando realmente alcançável dentro de paths/read_paths — AURUMCODE_SOURCE_DIR=... AURUMCODE_OUTPUT_DIR=... go run ./cmd/regenerate-docs — o mesmo padrão que o Public contract de AUR-424 já usava antes de aurumcode docs existir. Nenhuma frase deste spec, do aceite ou do relatório final afirma ter provado aurumcode docs --source.

O que mudou

Antes deste card, Rust e C# tinham extratores prontos (internal/documentation/extractors/rust.RustExtractor, internal/documentation/extractors/csharp.CSharpExtractor) que nunca eram registrados por padrão: ambos executam código do repositório documentado (cargo doc compila build.rs e proc-macros; dotnet build roda MSBuild, que executa <Exec>/<UsingTask>/.targets do projeto), então cmd/regenerate-docs/repo_code_execution.go os mantém atrás do opt-in explícito AURUMCODE_ALLOW_REPO_CODE_EXECUTION. Mesmo com o opt-in, nenhum dos dois roda no sandbox offline: cargo e dotnet/xmldocmd não existem na imagem pinada. Resultado: Rust e C# nunca geravam página nenhuma no caminho padrão.

A partir deste card, dois novos extratores — internal/documentation/extractors/rust.NativeExtractor e internal/documentation/extractors/csharp.NativeExtractor — leem os comentários de documentação diretamente do texto-fonte, sem iniciar nenhum subprocesso e sem tocar rede. RustExtractor/CSharpExtractor (as implementações que chamam cargo/dotnet) não foram alteradas: os testes fora de paths/read_paths deste card que fixam o contrato antigo (internal/documentation/extractors/tool_unavailable_test.go, tool_failure_test.go) continuam passando exatamente como antes, porque continuam testando rust.NewRustExtractor/csharp.NewCSharpExtractor diretamente — dois construtores que este card não tocou. Ver "Contratos pré-existentes preservados" abaixo.

cmd/regenerate-docs/main.go registra o extrator nativo de cada linguagem a menos que o operador já tenha optado por AURUMCODE_ALLOW_REPO_CODE_EXECUTION=rust (ou csharp, ou ambos): os dois caminhos são mutuamente exclusivos por linguagem, porque extractors.Registry.Register recusa um segundo extrator para a mesma linguagem. Com o opt-in, o comportamento de antes deste card continua disponível sem mudança nenhuma (repo_code_execution.go está intocado).

Cobertura declarada (honestidade obrigatória)

Nenhum destes dois extratores tenta ser um compilador. O que cada um reconhece — e, igualmente importante, o que cada um não reconhece e portanto nunca aparece na página gerada — está documentado no comentário de pacote de cada arquivo (internal/documentation/extractors/rust/native.go, internal/documentation/extractors/csharp/native.go) e resumido aqui.

Rust (internal/documentation/extractors/rust.NativeExtractor)

Reconhecido, com o comentário de doc associado:

  • pub fn (inclusive pub async fn, pub unsafe fn, e métodos pub fn dentro de um bloco impl, em qualquer indentação);
  • pub struct, pub enum, pub trait, pub mod, pub const, pub static, pub type;
  • um bloco impl Tipo { ... } / impl Trait for Tipo { ... } que tenha, ele mesmo, um comentário de doc (o comentário do bloco, não dos membros dentro dele — esses são capturados individualmente pela regra de pub fn acima);
  • os dois comentários de doc do Rust: /// (associado ao próximo item reconhecido) e //! (nível de módulo/crate, mostrado como introdução da página);
  • atributos #[...] entre um comentário de doc e o item que ele anota (ex. #[derive(Debug)]) não quebram a associação.

Não reconhecido — ausente da página gerada, nunca afirmado como suportado:

  • comentários de bloco /** ... */ (só comentários de linha são lidos);
  • itens gerados por macro: qualquer coisa produzida por macro_rules! ou por uma proc-macro é invisível a um scanner de texto, por construção — provado em tests/fixtures/docs/rustproject/src/lib.rs (define_record_fn!(record_internal)) e nos três níveis de teste deste card (unit, integration, e2e);
  • visibilidade restrita (pub(crate), pub(super)): deliberadamente excluída, nunca relatada como pública;
  • assinaturas que quebram linha (genéricos, cláusulas where, listas de parâmetros que continuam além da linha do próprio item) são truncadas naquela linha;
  • reexportações (pub use ...) e comentários de doc em campos de struct, variantes de enum, ou itens associados de trait individualmente.

C# (internal/documentation/extractors/csharp.NativeExtractor)

Reconhecido, com o comentário XML /// associado:

  • public class, public interface, public struct, public enum, public record (com qualquer combinação de sealed, abstract, static, partial, readonly, unsafe antes da palavra-chave);
  • métodos public, incluindo construtores (public NomeDaClasse();
  • propriedades public cujo acesso { get está na mesma linha da declaração;
  • os elementos XML padrão <summary>, <param name="...">, <returns> e <remarks>; elementos não reconhecidos dentro do comentário são ignorados, não rejeitam o comentário inteiro;
  • um comentário /// cujo conteúdo não é XML bem-formado: o extrator cai para o texto bruto do comentário (ainda mostrado na página, mas não quebrado em Summary/Parameters/Returns/Remarks) em vez de descartá-lo — provado por TestNativeExtractor_ExtractsRealSymbolsAndComments em internal/documentation/extractors/csharp/native_test.go.

Não reconhecido — ausente da página gerada, nunca afirmado como suportado:

  • comentários de bloco /** ... */;
  • campos, eventos, indexadores, operadores e implementações explícitas de interface;
  • membros com corpo de expressão (public int X => 1;) e auto-propriedades cujo acessor não seja literalmente { get na própria linha da declaração (por exemplo { get; set; } quebrado em várias linhas, ou propriedades com corpo =>);
  • declarações genéricas de tipo/método cuja assinatura quebra a linha da própria declaração.

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_ALLOW_REPO_CODE_EXECUTION Lista separada por vírgula (rust, csharp, ou ambos). Quando uma linguagem está nessa lista, o extrator nativo deste card não é registrado para ela: o RustExtractor/CSharpExtractor antigo (que executa cargo/dotnet) é registrado no lugar. Ver cmd/regenerate-docs/repo_code_execution.go.

Exit codes

Estes são os exit codes do próprio NativeExtractor.Extract (biblioteca) e do aceite deste card (tests/acceptance/AUR-427.sh), não os do binário completo cmd/regenerate-docs (que soma o resultado de todas as linguagens):

  • Extract retorna error != nil apenas para uma entrada inválida (idioma errado, diretório de origem inexistente) ou cancelamento de contexto; um arquivo individual que falhar ao escrever sua página é reportado em ExtractResult.Errors e não interrompe os demais.
  • tests/acceptance/AUR-427.sh: ver "Exit codes do aceite" abaixo.

Exemplo executável (sem segredo)

Com o projeto Rust mínimo deste card em ./tests/fixtures/docs/rustproject (um struct, um enum, três fn públicas, todos com comentário de doc real, mais um item gerado por macro que o parser não vê):

$ AURUMCODE_SOURCE_DIR=./tests/fixtures/docs/rustproject AURUMCODE_OUTPUT_DIR=./site \
    go run ./cmd/regenerate-docs
...
✓ Source: .../tests/fixtures/docs/rustproject | Output: ./site | ...
📊 Generated 1 markdown file(s) in ./site:
   - site/rust/src__lib.md
aurumcode: result=ok docs=1 skipped=0 failed=0 languages_skipped=none ...

site/rust/src__lib.md contém o comentário de crate (//!), pub struct Entry/pub struct Ledger com seus campos e métodos, pub fn new_entry/add/balance_cents com seus comentários de doc reais, e pub enum EntryKind — símbolos e prosa reais extraídos do fonte. A função gerada por macro (record_internal) e o método privado (entry_count) não aparecem em lugar nenhum da página.

Nenhuma variável de ambiente com segredo é lida por este caminho de código.

Diagnóstico de falha

  • Zero páginas Rust/C# com exit "no extractor registered" ou "required tool not in PATH" (comportamento pré-correção): sintoma exato do defeito que este card fecha. Só volta a acontecer se AURUMCODE_ALLOW_REPO_CODE_EXECUTION incluir a linguagem (o extrator antigo, que ainda depende de cargo/dotnet, assume o lugar do nativo de propósito) — ver MUT-001 abaixo para a prova comportamental do caminho padrão.
  • Um símbolo existe no fonte mas não aparece na página: verificar a tabela de cobertura acima — macro, visibilidade restrita, comentário de bloco /** */, ou assinatura que quebra linha são as causas cobertas.
  • Página gerada vazia ou ausente: extractors.ConfirmOutputFile recusa 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.
  • Comentário C# /// malformado como XML: não é erro; a página mostra o texto bruto do comentário em vez de Summary/Parameters/Returns/Remarks estruturados.

Escopo desta correção: só os dois extratores nativos

Este card não altera RustExtractor/CSharpExtractor (as implementações cargo/dotnet), não altera o opt-in AURUMCODE_ALLOW_REPO_CODE_EXECUTION, e não altera nenhum outro extrator deste diretório (Go, Python, JavaScript/TypeScript, C++, Bash, PowerShell). cmd/aurumcode/docs.go (o subcomando aurumcode docs de AUR-426) também não é alterado — ver a "Nota de contrato" acima.

Contratos pré-existentes preservados (não alterados por este card)

Diferente de AUR-424 — que teve que reescrever tool_unavailable_test.go/tool_failure_test.go porque mudou o contrato de Validate() do extrator Go existente — este card não toca nenhum dos dois. Ele adiciona dois construtores novos (rust.NewNativeExtractor/csharp.NewNativeExtractor) em vez de mudar rust.NewRustExtractor/csharp.NewCSharpExtractor, então as linhas rust/cargo e csharp/dotnet/csharp/xmldocmd de internal/documentation/extractors/tool_unavailable_test.go e tool_failure_test.go continuam passando sem edição nenhuma. Esses dois arquivos estão fora de paths/read_paths deste card (ver .board/cards/ready/AUR-427.md:12 — a segunda revisão do card removeu deliberadamente o diretório-pai internal/documentation/extractors de read_paths, deixando só os arquivos específicos de que este card precisa), então o aceite sandboxed não os reexecuta diretamente — verificado localmente (go test ./internal/documentation/extractors/..., todos os pacotes verdes). O que o aceite sandboxed prova diretamente é mais estreito, mas real: a lane de regressão (tests/acceptance/AUR-427.sh::regression_case) roda go test ./internal/documentation/extractors/rust ./internal/documentation/extractors/csharp dentro do sandbox, que inclui extractor_test.go de cada pacote — os testes pré-existentes de RustExtractor/CSharpExtractor (TestNewRustExtractor, TestRustExtractor_Validate, TestRustExtractor_Extract_NoCargo, e os equivalentes de C#) — e assim confirma, dentro do próprio aceite, que os dois extratores antigos continuam intactos nos dois pacotes que este card de fato tocou.

internal/documentation/extractors/registry_test.go e output_confirmed_test.go também não são tocados e também passam localmente pelo mesmo motivo.

Limitação de infraestrutura medida: fixture C# checked-in fora de paths

paths deste card declara tests/fixtures/docs/rustproject mas nenhum diretório de fixture C# (.board/cards/ready/AUR-427.md:12). A cobertura de C# neste card vem de fixtures em memória, escritas em tempo de execução pelos próprios arquivos de teste (internal/documentation/extractors/csharp/native_test.go, tests/unit/AUR-427.go, tests/integration/AUR-427.go) e por tests/e2e/AUR-427.sh (que escreve um .cs em $run_dir antes de rodar o binário real) — nunca por um arquivo .cs commitado fora de paths.

Exit codes do aceite

  • 0 prova, com as seis lanes (nominal, unit, integration, e2e, regressão, mutação) executadas;
  • 1 comportamento ausente (AUR-427/AC-001/behavior-missing), afirmação falsa de cobertura (AUR-427/AC-001/false-claim), saída não determinística (AUR-427/AC-001/nondeterministic-output:...), vazamento de segredo (AUR-427/AC-001/secret-leak), regressão fora do diff (AUR-427/AC-001/regression:<rc>), ou mutação não rejeitada (AUR-427/AC-001/MUT-001/...);
  • 64 seletor desconhecido;
  • 69 infraestrutura (go ausente, module cache ausente, árvore não materializada).

MUT-001

Change: desligar o registro padrão de Rust (main.go's if !repoCodeOptIn["rust"] { vira if false {, que nunca é verdadeiro — o extrator nativo de Rust nunca é registrado, mas o corpo do bloco — e o import de rustExtractor que ele referencia — continua presente e o binário continua compilando: uma falha de compilação não conta como RED sob o TDD proof deste card).

Expected: o mesmo accept sai diferente de zero (AUR-427/AC-001/MUT-001/...) porque o binário mutante, rodado de verdade contra o fixture do card, produz zero páginas Rust e (como o fixture só tem fonte Rust) sai com exit não-zero pelo piso de degradação do AUR-425 — e a restauração (reverter a mutação, ainda no diretório mutante escrito por sed, nunca no candidato commitado) reproduz o GREEN exato do binário compartilhado não-mutado. A checagem textual de call-site do Integration (IntegrationAUR427) deliberadamente NÃO é usada como falsificador aqui: ela apenas confirma que a chamada rustExtractor.NewNativeExtractor() não foi apagada do texto-fonte, o que continua verdadeiro depois desta mutação (só a condição do guarda mudou, não a chamada) — ver o comentário em tests/acceptance/AUR-427.sh::mutation_case para o porquê dessa assimetria.

Prova

  • Unit tests/unit/AUR-427.go::TestAUR427: fixtures em memória para as duas linguagens, Validate() sempre nil, páginas reais com símbolos e comentários reais, ausência comprovada de símbolos privados/gerados por macro.
  • Integration tests/integration/AUR-427.go::IntegrationAUR427: registro real em extractors.Registry, extração do fixture Rust checked-in (tests/fixtures/docs/rustproject) duas vezes com digest idêntico (determinismo), fixture C# em memória, e verificação textual dos dois call sites em cmd/regenerate-docs/main.go.
  • E2E tests/e2e/AUR-427.sh::E2EAUR427: execução real do binário — Rust via fixture checked-in, C# via fixture efêmera escrita pelo próprio script — com determinismo (Rust, duas execuções), checagem de canário de segredo, e ausência comprovada dos símbolos não cobertos.
  • Acceptance tests/acceptance/AUR-427.sh::AC-001 via ./.board/bin/oci-run --profile bootstrap-readonly-v1 --card AUR-427, incluindo a lane de regressão (go test ./cmd/regenerate-docs ./internal/documentation/extractors/rust ./internal/documentation/extractors/csharp — os três subárvores de teste pré-existentes inteiramente dentro de paths deste card) e o MUT-001 descrito acima.

Todo comando Go destes scripts roda com teto de memória embutido no próprio script (ulimit -v 8388608, GOMEMLIMIT=2GiB, GOFLAGS='-mod=mod -p=1', go test -timeout 300s), para que um revisor que rode bash tests/acceptance/AUR-427.sh diretamente tenha a mesma proteção.