Pular para conteúdo

AUR-467: the review looks at code as code and prose as prose

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

The defect, as measured

Run against the real gateway on 2026-08-14 over a real commit that changed 26 files, 15 of them .mjs:

AGENTS.md:115: [error]   'o que NAO fazer' contem instrucoes redundantes  (quality/dead-code)
AGENTS.md:134: [info]    a secao de invariantes e longa                   (quality/long-function)
AGENTS.md:142: [warning] as regras de invariante sao complexas            (quality/high-complexity)
AGENTS.md:58,156: [info] termos poderiam ser mais descritivos             (quality/poor-naming)
AGENTS.md:78,164: [warning] falta tratamento de erro                      (quality/missing-error-handling)

Seven findings, all seven on AGENTS.md (markdown), every one a code-quality rule id applied to prose. None of the fifteen .mjs files got a comment.

The fix

internal/prompt now classifies each diff file via internal/analyzer's own GetLanguageCategory (reused, not reimplemented) and excludes documentation-classified files from the pool that competes for the code token budget and receives the code rule catalog (filetype.go). The exclusion is never silent: coverage.go renders a "Review Coverage" section into the assembled prompt, and mirrors the same counts in PromptParts.Meta (code_files_total, code_files_complete, code_files_partial, code_files_omitted, prose_files_excluded), naming every documentation file excluded and every code file a tight budget could not fit.

Unrecognized extensions (.txt, .vue, an extensionless file) classify as code, not prose — DetectLanguage returns "unknown", and GetLanguageCategory("unknown") is "other", never "documentation". Treating unknown as prose would silently stop reviewing unmapped source, which AC-002 forbids.

Known gap, declared and not fixed (internal/analyzer is read_paths, not owned by this card): internal/analyzer.buildExtensionMap has no entry for .markdown (the long form of the extension) — only .md and .mdx map to "markdown", and .rst to "restructuredtext". A .markdown file, and extensionless README/LICENSE/CHANGELOG files, therefore classify as "unknown" → code, and still receive the code rule catalog. This is the same fail-closed default as .txt, applied to files that are usually pure prose in practice — a real gap, not a made-up one. Fixing it requires editing internal/analyzer's extension map, out of this card's paths.

This card does not build a prose rule catalog (Non-goals): documentation still has no rule_id catalog of its own, and that absence is what the coverage section now states.

The measured third cause

tests/unit/AUR-467.go::MeasuredCauseOfMJSOmission reconstructs (not replays — the original diff was never captured) why the .mjs files got zero comments, against the exported budgeting primitives directly:

  1. Ordering. determineFilePriority (budgeting.go) gives a documentation file the same PriorityHigh tier as code, so ties break on SortKey, the file path. "AGENTS.md" (leading byte 0x41) sorts before any lowercase path, so its hunk is offered to TrimToFit first.
  2. Starvation. TrimToFit breaks at the first segment that no longer fits rather than skipping it and trying the next, smaller one. Once AGENTS.md's hunk fills the budget, every later segment — the fifteen .mjs files — is dropped entirely, not truncated.

Both mechanisms are diagnosed, not independently patched: excluding prose before TrimToFit runs removes cause 1 for this shape of diff and frees the whole budget for code, which is what the same test's post-fix half proves. Cause 2 (break vs. continue) is still live for an all-code diff whose first-sorted file is oversized — flipping it would change which code files get reviewed today and fights budgeting_test.go; left as a follow-up.

Post-ff64e18 adversarial review: two blockers fixed

Blocker 1 — the coverage declaration was not itself budgeted. The first cut appended renderCoverageDeclaration's text to userContent after TrimToFit had already closed the token budget, and Meta["estimated_tokens"] summed baseTokens plus the trimmed segments only, never the declaration. Measured: an 8-omitted-file diff at MaxTokens=1500 assembled to ~1546 estimated tokens (3% over) while Meta reported 1419 — an undercount that grows with the number of omitted files, so the worst overshoot lands exactly when the budget is already tightest. Fixed by maxCoverageDeclarationTokens (coverage.go): it renders the declaration's WORST CASE for this diff's file set (every code file classified "omitted", the state that puts every path on its own bullet line — the maximum bullet count the renderer can ever emit, since a "complete" file contributes none) and reserves that many tokens out of the content budget before TrimToFit runs. Meta["estimated_tokens"] is now computed directly from the final System+User text BuildPrompt returns, not a partial sum.

Blocker 2 — a file was "covered" if any one of its hunks survived. coveredFiles (removed) checked file presence, not hunk completeness: a 2-hunk file whose second hunk TrimToFit dropped still read as code_files_omitted=0, and nothing was ever declared about it — silence on partial coverage, which this card's own Outcome forbids. Coverage is now tracked per hunk (coveredHunkCounts) and every code file is classified into exactly one of three states — complete (every hunk present), partial (some hunks present, some cut), omitted (zero hunks present). partial files are named in the declaration with their hunk fraction (path (k/n hunks)) and counted separately in Meta["code_files_partial"]; Meta["code_files_reviewed"] no longer exists, replaced by the three explicit counters (code_files_complete/code_files_partial/code_files_omitted).

What this card does not surface

PromptParts is this package's only public output; cmd/aurumcode and internal/review are read_paths, not paths. The coverage declaration lands in the assembled prompt content and in Meta, not on the CLI's own stdout — printing it there needs a follow-up card that owns cmd/aurumcode.

Selectors, each its own program

  • tests/unit/AUR-467.go::TestAUR467 — AC-001/002/003 on BuildPrompt directly, plus the ordering/starvation reconstruction.
  • tests/integration/AUR-467.go::IntegrationAUR467 — mixed prose types (.md, .rst), Meta arithmetic across three budgets, a no-prose control, and the AUR-461 rule catalog still present in System.
  • tests/e2e/AUR-467.sh — no-regression control: the real binary against tests/fixtures/repos/git-demo/repo.git (an all-code diff) still reports its fixture finding and closes --fail-on. AC-001/003 are not observable through a canned LLM fixture, since the defect is entirely in what gets sent to the model.
  • tests/acceptance/AUR-467.sh::AC-001 — a generated program calls the real BuildPrompt against a diff shaped like the measured commit and greps its output; no git, no python3. AC-001-MUT-001 reverts the prose exclusion; AC-003-MUT-002 suppresses the coverage line. Both must go red.