AUR-443 — a first-time user discovers aurumcode without reading source
Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.
Outcome: a user who has never read the source code discovers what
aurumcode does and can run a first review. Dogfooding on 2026-08-13
measured a first-use score of 3/10 against the real binary and found seven
concrete defects. This card fixes five of them, and investigates and
records two as deliberately unchanged because fixing them would break a
published, done card's contract. It changes only help/error/exit
surfaces: aurumcode review --base's findings output and aurumcode
docs's output are byte-for-byte unchanged (see "Compatibility" below).
Command
aurumcode --help | -h | help
aurumcode --version | version
aurumcode review --help | -h
aurumcode docs --help | -h
1. Top-level help
Before this card, aurumcode --help and aurumcode -h both failed:
aurumcode: unknown command "--help", exit 2 — there was no top-level
help at all, only each subcommand's own --help, and a user had to
already know review or docs existed to find those. aurumcode --help
(-h, or the bare word help) now lists every subcommand with a one-line
summary and one runnable example, on stdout, exit 0:
$ aurumcode --help
usage: aurumcode <command> [flags]
Commands:
review Review a git diff with an LLM (or the project's deterministic security rules) and print, gate on, or publish the findings.
docs Generate project documentation from source code.
Run "aurumcode <command> --help" for that command's own flags.
Example:
aurumcode review --base HEAD~1
$ echo $?
0
aurumcode with no arguments at all is unchanged: still a usage error
(usage: aurumcode <review|docs> [flags], stderr, exit 2) — the user
asked for nothing, so nothing was fulfilled; --help must be explicitly
requested to count as a fulfilled request (see "3." below). An unknown
command (aurumcode frobnicate) is likewise unchanged: stderr, exit 2.
2. --version
Before this card there was no version and no --version; the only way
to learn which build was running was to read go.mod/git log.
aurumcode --version (or aurumcode version) now prints a build-stamped
version string, on stdout, exit 0:
$ aurumcode --version
aurumcode dev
$ echo $?
0
dev is the default (cmd/aurumcode/main.go's var version = "dev")
because this reconstruction publishes no numbered release yet. A future
release pipeline injects a real value with no other code change, via Go's
standard -ldflags -X mechanism:
$ go build -ldflags "-X main.version=1.2.3" -o aurumcode ./cmd/aurumcode
$ ./aurumcode --version
aurumcode 1.2.3
3. One --help convention for every subcommand
Before this card, the same binary disagreed with itself: review --help
printed usage to stderr and exited 2 (the generic flag.Parse
error path, unchanged since AUR-430); docs --help printed usage to
stdout and exited 0 (AUR-426's deliberate choice, documented in
docs/specs/AUR-426.md's "--help's own convention" as intentionally
not retroactively applied to review). Both are Go conventions in
active use; the two-mindedness inside one binary was the actual defect.
The convention is now the same everywhere in this binary, at the
top level and both subcommands: an explicitly requested --help (or
-h) is a fulfilled request — stdout, exit 0. A genuine usage error
(an unrecognized flag, a value that will not parse) is a refusal —
stderr, exit 2.
$ aurumcode review --help >/dev/null; echo $?
0
$ aurumcode review --help 2>&1 1>/dev/null | wc -c # nothing on stderr
0
$ aurumcode review --bogus 1>/dev/null; echo $?
2
review's flag descriptions and every other published review behavior
(--base, --fail-on, --modelo, --seguranca, --pr, --limite,
--check) are untouched; only the channel and exit code --help itself
produces changed (cmd/aurumcode/main.go's runReview, mirroring the
buffered-then-dispatched pattern runDocs already established).
4. The provider-missing message shows the fixture's shape
Before this card, a first run without a configured provider (the common case for a user who has cloned the repository and typed the example command) failed with the correct diagnosis but no way to act on it without reading source:
aurumcode review: no LLM provider configured: set AURUMCODE_LLM_FIXTURE for offline use, or LLM_API_KEY and LLM_BASE_URL for a live provider
— naming the environment variable, but not what a valid file at that path
looks like. The message now shows the minimal JSON shape inline and
points at a real, versioned, already-committed example the user can point
AURUMCODE_LLM_FIXTURE at directly, with no source reading required:
$ aurumcode review --base HEAD~1
aurumcode review: no LLM provider configured: set AURUMCODE_LLM_FIXTURE=<path> to a JSON file shaped like {"issues":[{"file":"<path>","line":<n>,"severity":"error|warning|info","message":"<text>"}]} for offline use (see tests/fixtures/review/known-problem-response.json for a worked example), or set LLM_API_KEY and LLM_BASE_URL for a live provider
$ echo $?
1
$ AURUMCODE_LLM_FIXTURE=tests/fixtures/review/known-problem-response.json aurumcode review --base HEAD~1
config/demo-tokens.txt:4: [error] A credential-shaped value was committed in plain text (DEMO_API_TOKEN).
$ echo $?
0
reportModelUnavailable (the --modelo-specific unavailable-model
message) gets the identical pointer for the same reason. Both messages
are printed through the AUR-432 redaction writer; cmd/aurumcode/redaction_wiring_test.go's
TestStderrLinesSurviveTheRedactionWriter pins both as filter-identity —
neither file/line/severity/message nor the fixture's path is a
key/value shape the AUR-009 filter's secret-key-name rules ever match, so
the example itself is never rewritten to [REDACTED] in front of the
user.
5. --limite's refusal exit code: investigated, left unchanged
The dogfooding note observed that a --limite refusal exits 1,
indistinguishable from "the tool broke," while --fail-on's gate has its
own exit 3. This card investigated giving --limite's refusal a
dedicated exit code and did not make the change, because AUR-433 is
already done and its published contract pins exit 1 for exactly this
outcome in three places:
tests/acceptance/AUR-433.sh'snominal_case, the over-limit case:[[ "$rc" -eq 1 ]] || fail over-limit-wrong-exit.- The same function's margin/boundary case:
[[ "$rc" -eq 1 ]] || fail boundary-wrong-exit. mutation_case's restoration check:[[ "$rc" -eq 1 ]] || fail 'MUT-001/restoration-broken', and the mutant assertion[[ "$rc" -ne 1 ]] || fail 'MUT-001/not-rejected'.
docs/specs/AUR-433.md's own exit-code table independently documents the
same thing: "1 | The review failed, unchanged from AUR-430/431/435/436,
now including: the assembled request's cost would exceed --limite. The
model was never called." Any exit code other than 1 for this outcome
fails all three assertions above and contradicts the published spec. Per
this card's own instructions, that is a stop condition, not a defect to
route around: cmd/aurumcode/cost.go is untouched by AUR-443. This card's
own tests/unit/AUR-443.go re-asserts the exit code directly
("--limite refusal is still exit 1"), and the builder additionally ran
tests/acceptance/AUR-433.sh against this candidate directly in the
worktree and confirmed it still exits 0 (recorded in this card's
evidence). tests/acceptance/AUR-433.sh itself is not a read_path for
AUR-443, so tests/acceptance/AUR-443.sh cannot stage and re-run it
inside the sealed acceptance profile; that check is done out of band,
against the full checkout, not nested inside AUR-443's own sandboxed
acceptance.
If a distinct exit code for a budget refusal is still wanted, it is a
follow-up card's job: one that is allowed to touch AUR-433's own
paths (tests/acceptance/AUR-433.sh, docs/specs/AUR-433.md) and
re-publish that contract deliberately, not one that changes it as a side
effect of a UX pass.
6. Git-repository and ref errors: one clean, actionable message
Before this card, a repository that could not be opened produced a
literally duplicated phrase, because cmd/aurumcode's computeDiff
wrapped internal/analyzer.OpenRepo's own already-complete message with
another "is not a git repository" prefix:
aurumcode review: /tmp is not a git repository: not a git repository (no .git dir and /tmp is not bare): /tmp
and a ref that does not resolve leaked implementation detail that means
nothing to a user who has not read internal/analyzer: a raw filesystem
path on the pure-Go backend (the one this card's own sealed acceptance
profile always exercises — no git binary in bootstrap-readonly-v1),
aurumcode review: computing diff does-not-exist..HEAD: resolving base ref "does-not-exist": resolving ref refs/heads/does-not-exist: open /path/to/repo/.git/refs/heads/does-not-exist: no such file or directory
or git's own "fatal: ..." wording on the git-binary backend (used when a
git binary happens to be on PATH, e.g. an interactive developer
shell):
aurumcode review: computing diff does-not-exist..HEAD: resolving base ref "does-not-exist": resolving ref "does-not-exist": git rev-parse --verify does-not-exist^{commit}: fatal: Needed a single revision
— two different leaks of internal detail for the same user mistake,
depending on an implementation choice (OpenRepo's backend selection) the
user cannot see. Both are now one clean, actionable sentence, identical
regardless of which backend answered:
$ aurumcode review --base HEAD~1 # run outside any git repository
aurumcode review: /tmp is not a git repository (no .git directory here, and this path is not itself a bare repository)
$ echo $?
1
$ aurumcode review --base does-not-exist
aurumcode review: ref "does-not-exist" not found in this repository: expected a branch name, HEAD, a 40-character commit SHA, or a "<ref>~N" parent expression
$ echo $?
1
internal/analyzer is a read_path for this card (frozen, not owned by
AUR-443), so the cleanup lives entirely in cmd/aurumcode/main.go:
computeDiff no longer wraps OpenRepo's error with %w (its one error
case is fully known — see internal/analyzer/gitrepo.go's OpenRepo),
and a new cleanRefError helper recognizes the two prefixes
analyzer.Repo.Diff always wraps with ("resolving base ref %q: " /
"resolving head ref %q: ") together with the two concrete leaks measured
above (a *fs.PathError in the chain, or git's own "Needed a single
revision"/"unknown revision or path" wording) and replaces them with the
single sentence shown above. Any other Diff failure (a corrupted tree,
an ambiguous parent count, ref indirection too deep) is not concretely
measured by this card's dogfooding and keeps its pre-existing, more
verbose wrapped message — this card fixes the two measured leaks, it does
not invent a new taxonomy of ref errors that was never observed.
6.1. Post-review correction: permission denied is not "not found"
Independent review found that the first cut of cleanRefError matched
any *fs.PathError as "no such ref," which also matches a ref that
exists but cannot be read — EACCES, not ENOENT. Reproduced by
chmod 000 on a bare fixture's refs/heads/main and running as a
non-root uid (the same shape a locked-down runtime like
bootstrap-readonly-v1, or this reviewer's repro at uid 65534,
naturally has): the command answered ref "main" not found, exit 1 — a
confident, wrong diagnosis that directly contradicts this file's own
"never silently hidden behind a guessed diagnosis" promise. The
pre-AUR-443 binary's leaked, verbose message at least contained the
words "permission denied"; this card's first cut regressed that to a
clean but false answer.
Two separate defects, because the two backends leak differently:
- Pure-Go backend:
errors.As(err, &pathErr)alone cannot distinguishEACCESfromENOENT— both are*fs.PathError. Fixed by additionally requiringerrors.Is(err, fs.ErrNotExist). - git-binary backend:
git rev-parse --verify <ref>^{commit}prints the byte-identicalfatal: Needed a single revisionfor both causes — verified directly (git rev-parse --verifyagainst an unreadable ref vs. a missing one produces the same stderr text). No string-matching fix on git's own output can ever tell them apart.
The fix that closes both: cleanRefError no longer infers "not found"
from either backend's ambiguous text alone. It first calls a new,
independent probe, refPathPermissionDenied(repoRoot, ref), which opens
the exact physical ref location either backend would have consulted
(.git/refs/heads/<ref>, or .git/HEAD for ref == "HEAD") directly from
cmd/aurumcode and checks whether that open fails with
errors.Is(err, fs.ErrPermission). This is independent of which backend
actually answered — it reads the same file cmd/aurumcode already knows
the location of, so it correctly classifies the failure for both
backends with one code path, without editing internal/analyzer (a
read_path). The probe is conservative: a raw 40-character SHA is
skipped (no loose-ref file to check), and any outcome other than a
confirmed permission error — the file does not exist, or it opens fine —
falls through to the existing (now-corrected) "not found" heuristic, so
the probe never manufactures a diagnosis it cannot back up.
$ chmod 000 .git/refs/heads/main # as a non-root user
$ aurumcode review --base main
aurumcode review: permission denied resolving ref "main" in this repository: this process cannot read the ref's storage under .git (or the bare repository root) -- check its file permissions
$ echo $?
1
Verified identical on both backends (tests/integration/AUR-443.go's
"git-binary backend: the same unreadable ref is also a permission
message" subtest asserts the git-binary path's answer for this exact
scenario, alongside the pure-Go path). tests/unit/AUR-443.go's biting
test ("a ref that exists but is unreadable: permission message, never
ref-not-found") additionally asserts the negative: the message must
not contain "not found", and the genuinely-missing-ref test
("a ref that does not resolve") asserts the opposite negative — it must
not contain "permission denied" — so the two outcomes cannot silently
swap.
6.2. Post-review correction: a directory is not "not found" either
A second, independent review pass (vector 3: "try to fool the logic") found
a third distinct cause that fell through §6.1's fix into the same
raw-chain leak: --base feature against a repository whose only branch
under that name is the hierarchical feature/sub — no literal feature
ref exists, only the directory refs/heads/feature/ holding sub —
resolves refs/heads/feature to a directory, not a ref file. On the
pure-Go backend, os.ReadFile on a directory fails with EISDIR, which
is neither fs.ErrNotExist nor fs.ErrPermission — so §6.1's two checks
both correctly said no, and the code fell through to the one remaining
raw %w wrap, reproducing exactly the leak this card exists to close:
aurumcode review: computing diff feature..HEAD: resolving base ref "feature": resolving ref refs/heads/feature: read /tmp/.../refs/heads/feature: is a directory
— containing the literal refs/heads/ path tests/acceptance/AUR-443.sh
already treats as a leak everywhere else in this file, just not (before
this fix) against this specific scenario. The git-binary backend, for the
same input, already said a clean but imprecise ref "feature" not found
(git's own rev-parse folds this case into the same generic "no single
revision" outcome) — so the two backends disagreed, and only the
pure-Go one (the one bootstrap-readonly-v1 actually exercises) leaked.
The structural defect: cleanRefError, once it has matched the
"resolving base ref " / "resolving head ref " prefix, is already
committed to explaining a specific, named ref's resolution failure —
falling through to the raw wrap at that point was always going to leak
something for whatever cause the two existing checks did not
recognize, EISDIR was just the first one review found. The fix removes
the fallthrough entirely: every branch past the prefix match now returns
a clean, ref-scoped sentence, including a new fallback
("could not resolve ref %q in this repository") for a genuinely
unclassified cause, and a new refPathIsDirectory(repoRoot, ref) probe —
sharing refPathCandidate with refPathPermissionDenied, the same
direct-filesystem-probe approach §6.1 already established — checked
before the not-found heuristic:
$ aurumcode review --base feature # only refs/heads/feature/sub exists
aurumcode review: ref "feature" is not a branch: a directory of refs by that name exists (a hierarchical branch namespace, e.g. "feature/...") -- name the exact branch, not a namespace prefix
$ echo $?
1
Probing first, rather than only patching the pure-Go backend's own error
text, makes both backends agree on this more precise answer — the
git-binary backend's previous "not found" is now also replaced by "is not
a branch," not merely left alone, since the probe runs regardless of
which backend answered. Verified identical on both backends
(tests/unit/AUR-443.go and tests/integration/AUR-443.go each add a
subtest asserting the pure-Go and git-binary messages match byte for
byte). tests/acceptance/AUR-443.sh's nominal case re-runs its two
existing leak greps (no such file or directory, refs/heads/) against
this exact scenario, per the review's instruction, so a future regression
here cannot pass unnoticed.
7. The model response's unused summary field: investigated, left unchanged
The dogfooding note observed that pkg/types.ReviewResult.Summary
(parsed by internal/prompt.ResponseParser from the model's "summary"
field, redacted by internal/review.redactReviewResult) reaches
cmd/aurumcode's result and is never printed — silent dead code from
this command's point of view. This card's own instructions require a
decision: print it (and document it) or remove it from the parse; never
leave it silently dead.
Neither branch is reachable within this card's paths. The evidence:
- Printing it is blocked by a published,
donecard's byte-pinned contract.tests/acceptance/AUR-426.sh'sreview-contract-brokencheck andtests/unit/AUR-426.go's"aurumcode review's published contract is untouched"subtest (tests/unit/AUR-426.go:319) both runaurumcode review --base HEAD~1against the fixture{"issues":[],"summary":"Nothing to report."}— a non-emptysummary— and assert stdout is exactly"No issues found.\n", byte for byte. PrintingSummaryon a successful review, even conditionally on it being non-empty, changes that byte sequence and fails adonecard's own acceptance the next time it runs. This card's own instructions independently forbid it a second way:review --base's findings output is pinned not to change content, and a summary line is neither a help, error, nor exit-code surface. - Removing it from the parse is outside
paths. The field is parsed ininternal/prompt/parser.go'sParseReviewResponseand populated inpkg/types.ReviewResult; both areread_pathsfor AUR-443, notpaths— this card cannot edit either file.
The resolution: the decision is made and recorded, not silently
re-deferred. cmd/aurumcode/main.go's runReview carries a comment at
the exact site result is consumed, citing this section and the two
acceptance assertions above, so the omission reads as a deliberate,
investigated choice instead of an unexplained gap. No behavior changed.
2026-09-13 postscript (AUR-490): this decision is superseded, on
purpose, for review --base specifically. AUR-490's own Acceptance
required --base to carry the same summary and Mermaid diagram --pr
already produced, "sem configuração" -- the exact promise this product's
README and site make and --base did not keep. That cannot coexist with
the byte-exact "No issues found." contract above: a run's stdout cannot
both always include a rendered summary and, for the zero-issue case,
consist of nothing but that one line. AUR-490 chose to keep the promise
and retire the byte-exact contract, not the reverse -- the whole reason
that card exists is that the local path was silently missing what the
product already advertises.
Two things independently made this safe to decide without reopening this card's own investigation above:
- The blocking test no longer runs.
tests/acceptance/AUR-426.sh'sreview-contract-brokencheck depends oncmd/regenerate-docsandtests/unit/AUR-426.go, both removed by the later review-only pivot (670c7f6) -- before AUR-490 touched anything, this acceptance was already unable to execute past its owndocsinvocation. Retiring that now-dead script is tracked separately; it is not evidence either way about whether the summary should print, only about why nothing caught this sooner. - The reversal is recorded at the site it happens, not just here:
cmd/aurumcode/main.go'srunReview, at the same call site this section originally annotated, now explains whyrenderLocalReport(which includes the summary) prints unconditionally, citing this postscript.
The exit-code table below has the matching amendment for the "no provider configured" row.
Exit codes (all subcommands)
| Code | Meaning |
|---|---|
0 |
--help/-h/help (top-level or either subcommand) or --version/version: the request was fulfilled. |
0 |
review or docs ran and did what it promises (unchanged from AUR-430/431/435/436/438/439/433/441/426). |
1 |
A real failure: not a git repository, a ref that does not resolve, the model call failed, the response could not be parsed, --limite's estimate exceeded the ceiling (see "5." above) — all unchanged in meaning, some reworded per "4." and "6." above. "No provider configured" is no longer unconditionally in this row for review --base (AUR-490, see "7."'s postscript): that case now runs deterministic analysis and exits per --fail-on/--check/--exigir-qualidade against what it found — 0 when nothing crosses the configured threshold, 3 when something does. It remains exit 1 for the PR path (--pr) and for --base whenever --modelo was explicitly given and the named provider could not be reached. |
2 |
Usage error: an unrecognized flag, a required flag missing, a value that will not parse — including an explicitly requested --help that fails to parse for an unrelated reason (never for --help/-h itself, see "3." above). |
3 |
Unchanged from AUR-431/AUR-439: the --fail-on or --check gate closed on a grave finding. |
Compatibility, migration, rollback
- Compatibility.
aurumcode review --base's findings output (stdout content: notices, findings, the security section, the cache-reuse note, the cost lines) andaurumcode docs's output are byte-for-byte unchanged. This card touched only: (a) new top-level--help/-h/helpand--version/versionhandling inrun(), previously unreachable code paths (every one of those tokens previously fell through to "unknown command", exit2); (b)review's own--help/-hchannel and exit code (stderr+2 → stdout+0), which no published spec or test pinned before this card (verified: no acceptance, unit, integration or e2e file for AUR-430/431/433/435/436/438/439/441 exercisesreview --helpat all); (c) two provider-missing stderr messages (extended, not shortened — every reader relying onstrings.Contains(..., "no LLM provider configured"), e.g.tests/unit/AUR-436.go:198, still matches); (d)computeDiff's git-repository/ref-resolution error text (never asserted verbatim by any published spec or test — verified by the same search).tests/acceptance/AUR-433.sh,AUR-439.sh,AUR-441.shandAUR-426.share notread_pathsfor AUR-443, so this card's own sandboxedtests/acceptance/AUR-443.shcannot stage and re-run them; instead,tests/unit/AUR-443.goandtests/e2e/AUR-443.shre-assert the specific published invariants those four cards pin (byte-identicalreview --baseoutput including with a non-emptysummary, the--limiteexit code, the finding line shape), and the builder separately ran all four sibling acceptance scripts directly against this candidate in the worktree and confirmed exit0in each (recorded in this card's evidence, not nested inside the sealed acceptance run). - Migration. Not applicable — no data or state format changes.
- Rollback. Revert only
paths; no state external to this repository is published by this card.