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.

Every cut is visible. Elisions are printed as… [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 .