Pular para conteúdo

AUR-459 — O formato que o prompt pede e o que o parser le

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

O defeito

internal/prompt/templates/review.md mostrava dois schemas de achado no mesmo exemplo de saida: line_comments primeiro, issues depois. O parser (internal/prompt/parser.go) lia so issues. O modelo escolhe o primeiro que ve — medido contra o gateway real (resposta de 6042 chars, finish_reason=stop): o JSON devolvido traz line_comments, iso_scores e summary, nunca issues. Consequencia medida: um diff com tres segredos plantados imprimia No issues found. com exit 0. O modelo achou e descreveu os tres; a ferramenta jogou fora e afirmou o contrario com confianca.

O conserto

  1. Um schema so no prompt. O template mostra issues, iso_scores e summary. Os blocos line_comments, file_comments e commit_comment sairam do exemplo. Nenhum deles era consumido: LineComments nunca era lido em lugar nenhum, e FileComments/CommitComment so eram tocados pela redacao (internal/review/reviewer.go) antes de serem descartados. O texto de nivel de arquivo e de commit passa a ser pedido no campo summary. O template tambem passa a dizer que rule_id e obrigatorio e por que: achado sem rule_id resolvivel e descartado pelo portao do AUR-434 e nunca aparece.

Correcao apos review: a chave do merge era (file, line) e escondia achado

A primeira versao deste card casava comentario com achado so pela localizacao e justificava assim: "o registro de issues vence, e o mais rico". Executado, o argumento e falso — um nit de estilo em a.go:42 engolia um SECRET LEAK reportado na mesma linha via line_comments, e dois comentarios distintos numa linha colapsavam num so, sem contador e sem aviso. Varios achados dividem uma linha rotineiramente.

A chave passou a ser (file, line, mensagem normalizada) — caixa e espacos dobrados, entao o mesmo achado restado na outra forma ainda colapsa, mas achados diferentes na mesma linha sobrevivem todos. E os tres caminhos que nao adotam um comentario (repeticao de achado ja reportado, path vazio, body em branco) sao contados e nomeados em Metadata["parse_discard_warning"], que cmd/aurumcode imprime no stderr ao lado do aviso do AUR-448: aurumcode review: 2 line comment(s) not converted: 1 already reported as an issue, 1 with no path. Um card cuja resposta escolhida e "anuncie o descarte" nao pode descartar em silencio no proprio merge.

  1. O parser aceita as duas formas. adoptLineComments converte cada entrada de line_comments em types.ReviewIssue (path/file -> File, line -> Line, body/message -> Message, mais rule_id, severity e suggestion quando vierem; severidade ausente vira warning, o mesmo default que degradedExtract ja usa). E merge, nao substituicao, com a chave (file, line, mensagem normalizada) descrita acima: so o mesmo achado restado na outra forma colapsa, e o colapso e anunciado. Entrada malformada nao vira falha dura: bloco que nao decodifica e ignorado, entrada sem path ou sem body e pulada em vez de ir para validateReviewResult, que rejeitaria a resposta inteira por causa de um item lixo.
  2. A divergencia futura quebra o build. TestReviewTemplateMatchesParser extrai os campos de topo reais do template embarcado (le os bytes do proprio arquivo, nao uma copia) e exige exatamente canonicalReviewFields. TestAcceptedReviewFieldsAreConsumed prova, campo a campo, que cada nome de acceptedReviewFields muda o resultado do parse — uma lista escrita a mao e exatamente a classe de falha deste card, entao a lista sozinha nao valeria como prova.

A armadilha: nao trocar um falso negativo por outro

Um line_comment convertido normalmente nao traz rule_id, e o portao do AUR-434 (internal/review.enforceRuleCitations) descarta achado sem rule_id do catalogo embarcado. Converter sem mais nada devolveria No issues found. — o mesmo silencio, por outra porta.

Escolha: anunciar o descarte (a segunda das duas saidas admitidas pelo card). O aviso do AUR-448 ja faz isso: formatDiscardWarning conta quantos e por que, e cmd/aurumcode imprime a linha literal no stderr — aurumcode review: 1 finding(s) discarded: 1 with no rule_id. O usuario recebe a contagem e a razao, nunca silencio.

A outra saida (exigir rule_id dentro de cada line_comment) foi rejeitada por incoerencia: o template nao mostra mais line_comments, entao documentar um campo obrigatorio dentro de uma forma que o prompt nunca pede seria um contrato que ninguem e oferecido. O rule_id e exigido onde o prompt de fato pede — dentro de issues — e line_comments fica como alias tolerado para o modelo que viu um prompt antigo ou um .aurumcode/prompts/review.md do usuario. Quando esse alias traz rule_id, o achado sobrevive e aparece no stdout; quando nao traz, o descarte e anunciado.

Comando, saida e exit codes

aurumcode review --base HEAD~1, sem mudanca de contrato:

  • 0 — a review rodou. Achados no stdout, um por linha, <file>:<line>: [<severity>] <message> (rule <id>: <title>), ou No issues found.
  • 1 — a review falhou (provider ausente ou indisponivel, parse impossivel).
  • 2 — erro de uso.
  • 3 — --fail-on fechou o portao.

Exemplo executavel sem segredo, com o provider offline deterministico:

cat > /tmp/resp.json <<'JSON'
{"line_comments":[{"path":"config/demo-tokens.txt","line":4,"severity":"error",
  "rule_id":"security/hardcoded-secret","body":"credential-shaped value"}]}
JSON
AURUMCODE_LLM_FIXTURE=/tmp/resp.json aurumcode review --base HEAD~1

Diagnostico de falha: se o stdout disser No issues found. e o stderr trouxer N finding(s) discarded, o modelo reportou algo que o portao de regra recusou — o rule_id citado esta ausente ou nao existe no catalogo embarcado. Se o stdout estiver vazio e o exit for 1, nenhum provider foi configurado: a propria mensagem no stderr mostra a forma completa do fixture.

Limites declarados

  • internal/review/cache.PromptVersion continua "v1". Esse arquivo esta fora dos paths deste card, entao entradas de cache gravadas sob o prompt antigo continuam validas sob o prompt novo. Quem quiser reprocessar limpa o diretorio de cache (AURUMCODE_CACHE_DIR).
  • Os seletores do card sao TestAUR459, IntegrationAUR459 e E2EAUR459 (o card trazia ...AUR435 por erro de gabarito, corrigido fora deste worktree: TestAUR435 ja existe em tests/unit/AUR-435.go, no mesmo pacote unit, e redefini-lo quebraria a compilacao). Os programas Go rodam pelo bridge _test.go que a convencao do repositorio usa (veja tests/acceptance/AUR-448.sh), porque tests/unit/*.go e tests/integration/*.go nao sao _test.go e go test sozinho reportaria "no test files" e passaria vazio; o contrato mais fino vive em internal/prompt/schema_contract_test.go, no pacote do proprio codigo.
  • Cada seletor declarado roda o SEU programa: TestAUR459 -> tests/unit/AUR-459.go (o parser sozinho), IntegrationAUR459 -> tests/integration/AUR-459.go (a costura parser + portao de regra via GenerateReview, com FakeProvider), E2EAUR459 -> tests/e2e/AUR-459.sh (a decisao de CI: --fail-on fecha em achado convertido e NAO fecha em achado descartado). Seletor sem programa proprio sai 64: ausencia le-se como ausencia, nunca como um verde colhido das asserções do AC-001.
  • Nao existe segunda copia do prompt de review no repositorio: .aurumcode/prompts/ contem apenas documentation, entao internal/prompt/templates/review.md — o arquivo embarcado por templateFS e lido em runtime — e a unica lista de campos mantida a mao, e e ela que TestReviewTemplateMatchesParser guarda. O comentario de builder.go que fala em "build-time copy de .aurumcode/prompts/ review.md" descreve a origem historica do texto, nao um arquivo presente.

Prova

  • RED 1: reintroduzir o bloco line_comments no template → TestReviewTemplateMatchesParser falha nomeando os dois schemas.
  • RED 2: remover a chamada a adoptLineComments → TestAcceptedReviewFieldsAreConsumed/line_comments, TestLineCommentsDoNotDisplaceIssues e TestLineCommentsJunkIsSkippedNotFatal falham.
  • tests/acceptance/AUR-459.sh AC-001-MUT-001 reconstroi o binario com o defeito de volta e exige que o achado suma; se sobrevivesse, o aceite reportaria AUR-459/AC-001/MUT-001.