AUR-464 — Cabeçalho com nome do símbolo, não "## Documentation" repetido
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
Defeito medido
extractors/bash/extractor.go:127 e extractors/powershell/extractor.go:131,148
escreviam "## Documentation\n\n" como cabeçalho de CADA bloco de comentário
encontrado, sem nome de símbolo, assinatura, parâmetro ou retorno. Uma página
real gerada por esta ferramenta (.aurumcode/bash/run-docs-pipeline.sh.md,
gerada de uma versão anterior de run-docs-pipeline.sh) tinha N cabeçalhos
## Documentation idênticos, o que também quebra qualquer sumário navegável
construído sobre os títulos da página (N entradas iguais chamadas
"Documentation").
Forma adotada (a mesma dos extratores nativos de Go/Rust/C#)
Os três extratores nativos que já nomeiam símbolo —
extractors/go/extractor.go (### type X, #### func Y),
extractors/rust/native.go (### fn nome, ### struct nome, ...) e
extractors/csharp/native.go (### <kind> <assinatura>) — nunca repetem um
cabeçalho literal por bloco: cada símbolo real recebe seu próprio cabeçalho
com seu próprio nome, doc (se houver) e um bloco de código com a assinatura.
Nenhum deles repete cabeçalho fixo; o defeito do card é exclusivo dos
extratores Bash/PowerShell.
AUR-464 segue exatamente essa forma para Bash e PowerShell:
- Cada função reconhecida (
name() { ... }oufunction name { ... }em Bash;function Name { ... }em PowerShell) gera### function <nome>. - O comentário que precede a função, sem linha em branco entre o último
#e a declaração, vira o corpo dessa seção (Doc). - Uma função sem comentário ainda aparece — com assinatura, no bloco de código, e SEM PROSA (AC-003). Nada é inventado.
- Um comentário que precede outra coisa (uma instrução comum, o fim do
arquivo, ou está separado da próxima função por uma linha realmente em
branco) não pertence a nenhum símbolo. Ele nunca vira um cabeçalho falso
por bloco: todos os blocos "soltos" de um arquivo são coletados uma única
vez sob um cabeçalho real e não repetido,
## Script Notes.
Como ### function <nome> sempre carrega o nome real e distinto do símbolo,
o cabeçalho nunca se repete dentro da mesma página (AC-001) e o slug padrão
de título-para-âncora (usado pelo Jekyll/kramdown do site) produz âncoras
distintas para símbolos distintos (AC-002).
Separação comentário-solto vs. comentário-do-símbolo
Uma linha realmente em branco entre o último #/<# ... #> e a próxima
linha de código encerra o bloco pendente. Isso é deliberado: sem essa regra,
uma visão geral do arquivo separada de uma função apenas por espaço em
branco seria atribuída erroneamente àquela função como se fosse sua própria
documentação. Uma linha de comentário vazia (# sozinho, sem espaço antes
do próximo #) NÃO encerra o bloco: é o separador de parágrafo comum
dentro de um único comentário (como no próprio run-docs-pipeline.sh real
do repositório, linha 3).
Cabeçalho de arquivo vs. doc da primeira função (regra final, revisada três vezes)
Este é o ponto mais revisado do card, com três rodadas de rejeição.
Rodada 1 usava só len(page.Symbols)==0 && len(strayNotes)==0 e perdia o
caso "código real antes do comentário". Rodada 2 TROCOU esse sinal por
sawCode sozinho e voltou a quebrar — desta vez o caso "nota já resolvida
antes do comentário". A lição, registrada aqui para não se repetir: os
sinais não são substitutos um do outro, são complementares.
Regra final: um comentário pendente só é tratado como cabeçalho de
arquivo ambíguo — e vai para ## Script Notes em vez de virar o Doc da
função seguinte — quando as TRÊS condições valem ao mesmo tempo:
len(page.Symbols) == 0 && len(strayNotes) == 0 && !sawCode
len(page.Symbols) == 0: nenhuma função foi resolvida ainda.len(strayNotes) == 0: nenhum bloco de comentário anterior já foi resolvido como nota solta. Um bloco já resolvido em Notes já PROVA que a ambiguidade do arquivo foi encontrada e arquivada; nada depois dele pode voltar a ser "o cabeçalho do arquivo" — mesmo que nenhum código tenha rodado ainda.!sawCode: nenhuma linha realmente executável (função ou instrução) já rodou.sawCodeviratruena primeira linha executável do script e nunca volta afalse; shebang e linhas em branco nunca contam como código.
Tabela dos quatro casos (TestAUR464LeadingCommentFourCases)
| # | Forma do script | Resultado | Por quê |
|---|---|---|---|
| 1 | shebang + licença colada direto na 1ª função, nada mais | vai para Notes | nenhum símbolo, nenhuma nota, nenhum código ainda: ambíguo |
| 2 | set -euo pipefail (ou qualquer instrução), depois a doc |
anexa à função | sawCode verdadeiro: código já rodou, não é mais ambíguo |
| 3 | overview, linha em branco, depois a doc | anexa à função | strayNotes não vazio: a nota anterior já resolveu a ambiguidade do arquivo |
| 4 | a própria 1ª linha executável já é a função documentada, nada acima | vai para Notes | os três sinais ainda valem: sem símbolo, sem nota, sem código -- custo residual DECLARADO abaixo |
Casos 1 e 4 têm o MESMO resultado (Notes) pelo MESMO motivo estrutural: são indistinguíveis por posição e sintaxe de um cabeçalho de arquivo. Casos 2 e 3 têm o mesmo resultado (anexa) por motivos DIFERENTES -- um por código já ter rodado, o outro por uma nota já ter sido resolvida -- e é exatamente essa dupla razão que a condição em conjunção precisa capturar; usar só um dos dois sinais (rodadas 1 e 2 deste card) sempre deixa um dos dois descoberto.
A fixture exata do revisor (TestAUR464OverviewThenDocPreserved, ambos os
idiomas) é o caso 3: overview, linha em branco, # Greets a name.,
greet(). greet preserva sua doc porque a nota do overview já foi
resolvida antes dela ser alcançada.
Trade-off aceito e DECLARADO (o único que resta)
Depois deste conserto, o único custo real é o caso 4 da tabela: um script
cuja PRIMEIRA linha executável é uma função documentada, sem nenhum código
E sem nenhuma nota solta antes dela -- ex.: #!/bin/bash seguido direto de
# Greets a name. seguido direto de greet() { ... }, nada mais no
arquivo antes disso. Esse caso ainda perde o doc real da função para
## Script Notes, porque é estruturalmente indistinguível, por posição e
sintaxe, de um cabeçalho de arquivo (licença, copyright) colado do mesmo
jeito na primeira função. O card declara explicitamente: atribuir um
comentário ao símbolo errado documenta uma mentira, o que é pior do que um
símbolo aparecer sem prosa -- então, na ambiguidade genuína, a ferramenta
escolhe sempre o lado seguro (Notes), não o lado que "parece" documentar
mais. Isso é raro na prática (a maioria dos scripts reais tem ao menos um
set -e, um cd, um source, ou um comentário de overview antes da
primeira função), mas é real e fica registrado aqui, não escondido.
Nenhum outro custo restou depois deste conserto: os casos 1, 2 e 3 da tabela acima cobrem exatamente as duas rodadas anteriores de rejeição, e os dois já não perdem doc legítima.
Âncoras únicas depois da normalização (não apenas por contagem de base)
renderBashMarkdown/renderPowerShellMarkdown mantêm usedAnchors, o
conjunto de toda âncora FINAL já escrita na página (depois de qualquer
sufixo de desambiguação) — não uma contagem por âncora-base. Para cada
símbolo: calcula a âncora do cabeçalho puro (function <nome>); se já
está em usedAnchors, tenta function <nome> (2), (3), ... até achar uma
âncora livre, testando cada tentativa contra o conjunto real, não contra uma
contagem.
Isso importa porque uma âncora JÁ sufixada pode colidir tanto com o nome
puro de outro símbolo quanto com a âncora sufixada de um terceiro. Exemplo
medido (TestAUR464AnchorTrioUnique, trio foo/Foo/foo-2 nessa ordem):
foo→ âncorafunction-foo(livre) → usa.Foo→ âncora basefunction-foo(colide, minúsculas) → tentafunction Foo (2)→ âncorafunction-foo-2(livre) → usa.foo-2→ âncora basefunction-foo-2— que é EXATAMENTE a âncora que o passo 2 acabou de reservar → colide de novo → tentafunction foo-2 (2)→ âncorafunction-foo-2-2(livre) → usa.
Uma implementação que só contasse ocorrências da âncora-base (a versão
anterior deste card) teria reservado function-foo-2 duas vezes sem notar
a segunda colisão. run_docs (_) e run-docs (-) continuam distintos
de propósito: a classe de caracteres do slug preserva _ e - como
caracteres diferentes, então esse par nunca depende do laço de
desambiguação.
headingAnchor (bash e powershell) reconhece nomes de função com hífen
(my-func, foo-2): a classe de caracteres do símbolo (bashKeywordPattern
/bashParenPattern) foi ampliada para incluir -, alinhando com o padrão
que o PowerShell já aceitava.
Non-goals cumpridos
- O tema Jekyll (
just-the-docs) não foi tocado; só o Markdown gerado mudou. - Nenhuma prosa foi inventada: um símbolo sem comentário mostra assinatura e silêncio.
- Os extratores nativos de Go, Rust e C# (
extractors/go,extractors/rust,extractors/csharp) não foram tocados. Medido durante a construção: eles já nomeiam símbolo e nunca repetem cabeçalho — o defeito do card era exclusivo de Bash/PowerShell.
Comando
Este card não possui cmd/regenerate-docs (fora de paths/read_paths), só
os dois pacotes extratores e o normalizador. A prova de ponta a ponta chama
o mesmo entrypoint público que cmd/regenerate-docs chamaria —
bashextractor.NewBashExtractor(...).Extract(ctx, req) e
powershellextractor.NewPowerShellExtractor(...).Extract(ctx, req) — através
de um driver Go minúsculo escrito por tests/e2e/AUR-464.sh dentro da própria
árvore do módulo (necessário para o import de pacote internal/... ser
permitido) e removido ao final da execução.
O comando de aceite real é ./.board/bin/oci-run --profile bootstrap-readonly-v1
--card AUR-464, que por sua vez roda tests/acceptance/AUR-464.sh AC-001.