The pipeline
One pass, ten stages, no loops. Each stage narrows the log; the last four are the only ones that ever see fewer lines than the job actually printed.
- 1Acquire
providers/Fetch the failed run and its job logs over the provider API, or read a saved log from disk.
tuned by
provider, gitlab, github - 2Segment
providers/*/segmenter.pyCut the raw log into named sections. Each CI system frames a job differently; this is the last place that difference is visible.
tuned by
— - 3Map phases
core/phases.pyLabel each section with a phase: provision, prepare, fetch, script, post.
tuned by
phases - 4Attribute
core/attribution.pyDecide which phase actually failed, from job metadata and log structure. Pure, deterministic, no model.
tuned by
— (deliberately) - 5Denoise
core/denoise.pyStrip ANSI colour, collapse progress bars, fold repeated lines, drop configured noise.
tuned by
denoise - 6Extract
core/extract.pyRun every matcher over the blamed section and keep the windows they anchor. Every gap is marked.
tuned by
extraction.matchers - 7Budget
core/budget.pyShed the lowest-priority windows, then trim what survives to the token budget.
tuned by
llm.max_input_tokens - 8Explain
llm/One optional model call, given only the budgeted evidence. Answers why, never where.
tuned by
llm - 9Redact
core/redact.pyScrub secrets. Runs twice — on the prompt before it leaves the process, and on the finished report.
tuned by
redaction - 10Render
render/Terminal, report.md, report.json, and optionally one idempotent note on the MR/PR.
tuned by
output
Stages 5–7 are how a 40,000-line log becomes a few dozen lines worth reading —Evidence covers those. To see the same ten stages as a dataflow graph, with the module boundaries drawn in, go back tothe Concepts overview .
You can watch the whole thing run with ci-doctor analyze -v: it logs the section count, the attribution verdict and rule, and adenoise → extract → fit line with the surviving line count at each step.