Concepts

What the moving parts are called, what each one decides, and where it lives in the code. Read this section once and the configuration reference stops being a list of unexplained knobs.

The one rule everything else follows. Deterministic code decides where the job failed; the LLM only explains why. Phase attribution runs before any model is called, so a loud but harmless cache warning can never be blamed over the real exit code 1. If the classifier is wrong, that is a bug with a failing test — not a prompt to tune.

The shape of the whole thing

Where each module sits, and what actually flows between them. Note the two dotted edges: they are the only ones a model touches, and they carry evidence that has already been selected and redacted.

The shape of the whole thing Provider adapters segment the log and normalise the failure reason, then hand off across core/ports.py to a provider-neutral core that assigns phases, decides which phase failed, and denoises, extracts and budgets the blamed section into an EvidenceBundle. The report is built deterministically, optionally explained by one model call, scrubbed again, and rendered. PROVIDERS/ — NAMES A CI SYSTEM CORE/ — PROVIDER-NEUTRAL · PURE · OFFLINE OUTPUTS CORE/PORTS.PY — THE ONLY CONTRACT REDACTED VALIDATED IN Provider API or saved log --job-url · --log-file SEG Segmenter raw log → Section tree RSN Failure reason provider status → FailureReason PH Phases core/phases.py ATT Which phase failed core/attribution.py PIPE Blamed section only denoise · extract · budget BND EvidenceBundle all the report step may see REP Deterministic report llm/report.py OPT Explains why llm/ backend · optional RED Scrub again core/redact.py OUT Render terminal · md · json · mr LEGEND Focal Deterministic step Data in & out Optional Offline flow Model call
Solid edges are deterministic and offline. The dotted pair is the one optional model call.

The same code as layers

Three bands. Providers know their CI system and nothing else; core knows the domain and no provider; outputs render what core decided. The seam through the middle is the only way data crosses.

Why the middle band is sealed. grep -ri gitlab ci_doctor/core/ must return nothing, and CI fails if it does. That is what makes adding a CI system a matter of dropping in a directory rather than threading a third case through the classifier. The code map walks it file by file.

The invariants

These are enforced by tests, not convention. The first three are load-bearing: