Evidence
Attribution picks a section; this is everything that happens to it afterwards. Four passes, in order, each one narrowing what a reader — or a model — ever sees.
Denoise
Cutting volume before anything is selected. Per line: strip ANSI escapes, collapse\r progress-bar rewrites to their final state, optionally drop the runner's timestamp prefix, then drop configured noise patterns. Finally, runs of identical lines fold into one with a (×47) count.
A line that looks like an error is never removed by a noise pattern — the escape hatch that stops an over-broad rule from deleting the one line that explains the failure.
Matcher, window, priority
A matcher is a regex that recognises one tool's failure output. Where it fires, it anchors a window — the run of lines actually worth showing. Overlapping windows merge; the space between them is replaced by an explicit count.
# denoised log, blamed section
Compiling ledger v0.4.1
error[E0308]: mismatched types <- matcher anchors here
--> src/posting.rs:88:31 |
| | window: the lines kept
88 | let rate: Decimal = ... |
= note: expected `Decimal` <-
… [14 lines elided] … <- the gap is announced, never silent
error: could not compile `ledger`Priority decides who survives when the evidence exceeds the budget. Rank a pack by how diagnostic it is, not how loud: npm ERR! always trails the compiler errors that caused it, so it must lose to tsc. The full catalogue — all 35 shipped packs, what each one catches, and how to add your own — is on Matchers .
Budget
The evidence has to fit the model's input limit. Two passes, in order: shed whole low-priority windows, then trim whatever survived. Trimming keeps the tail, because the failure lives at the end of a log — and it announces what it dropped.
The order matters. Trimming alone would keep the tail of everything, and a tool that reports its cause before its own epilogue would lose the diagnosis and keep the complaint. Token counting is a four-characters-per-token estimate; no tokenizer is loaded, and nothing is downloaded to do it.
Redaction
Scrubbing secrets out of everything that leaves the process. A local regex set (private keys, glpat- tokens, AWS keys, Authorization: headers, credentials embedded in URLs) plus the literal values of any environment variable whosename looks secret — a stand-in for the CI's masked-variable list, so a token with no recognisable shape is still caught.
It runs twice: on the prompt before it is sent, and on the finished report before it is printed or posted. Once is not enough — a model can quote a secret straight out of the log it was shown. There is no entropy scanning and no network call; replacements read [REDACTED:<kind>] so the report stays legible.
The evidence bundle
Everything the report step gets, and all it gets: the blamed phase, its denoised and budgeted lines, one non-causal note per warning-only phase, job metadata, the token estimate, and whether anything was truncated. Truncation is always surfaced — a silent cut would be a lie about the evidence.
… [N lines elided] … wherever they happen — between windows, and again if the budget had to trim inside one. If you never see a marker, nothing was dropped.Watching this run is one flag: ci-doctor analyze -v prints adenoise → extract → fit line with the surviving line count after each pass, and whether the budget truncated. See the CLI reference .