latent.lint.engine¶
latent lint engine — file reading, waivers, rule dispatch, output, exit codes.
Stdlib ast + tokenize + pyyaml only. Never imports the rest of latent: it must lint a repo whose extras are not installed (ADR-003 §B).
Functions¶
compose_review_prompt¶
The packaged Tier-2 prompt with its generated rule table filled in.
The table is built from RULES instead of hand-copied into the markdown:
eight of the file's rows duplicated RULES[*].machinery and would drift
with no test to catch it.
exit_code¶
2 on any LT999 (engine error), 1 on any unwaived finding, else 0.
Baseline-blind; the CLI's ratchet path is :func:ratchet_exit_code.
format_github¶
One ::error annotation per unwaived finding, input order.
Grammar: ::error file={path},line={line}::{rule} {message}. Waived
findings are not annotated — a waiver is the accepted decision, and
re-surfacing it on the PR is exactly the noise waivers exist to remove.
format_json¶
CI telemetry: every finding (waived included) plus waiver and ratchet counts.
The only format that carries waived findings — text and github show
unwaived only, so a dashboard tracking waiver debt has exactly one
source. Key order is the wire contract and comes from these literals;
sort_keys stays off. Findings sort by (path, line, rule) and
exceedances by (path, rule) so two runs over the same tree produce
byte-identical output regardless of caller order.
format_text¶
One path:line rule message line per unwaived finding, input order.
collapse instead folds repeats of one (path, rule, message) into a
single line carrying the count and the lines it sits on; groups appear in
the input order of their first finding. Only render("text") asks for
that form — it is the one output a person reads. The default is
per-finding: see :func:latent.lint.rules.lt004 for the invariant.
lint_files¶
Lint the given files; the result includes waived findings (waived=True).
Only flow-dir modules (parent has catalog.yaml or parameters.yaml) are
checked; other files contribute zero findings. Selecting any module of a
flow dir also pulls in that dir's anchor module, because dir-level rules
report there. Known input errors — syntax errors, unreadable files,
corrupt yaml sidecars — become LT999 findings; exceptions from rule checks
propagate. A path outside repo_root raises ValueError: its finding key
would be a ../.. string that no baseline can match.
Waivers are applied here rather than inside a rule, and what they matched
is fed back to :func:_hygiene_findings — the whole file's findings have
to exist before a waiver can be called stale.
Naming a file is the caller's decision and always wins: this is the one
entry point that ignores config/lint.yaml's exclusions, which are a
discovery boundary (see :func:_excluded_dirs).
lint_paths¶
lint_paths(paths: Sequence[Path | str] | None = None, repo_root: Path | str | None = None, changed: bool = False, base: str = 'origin/main') -> list[Finding]
Select flow-dir modules and lint them with lint_files.
Selection modes, first match wins:
pathsis not None: lint exactly those roots — files pass through, directories are walked for flow dirs (flat per dir);changedandbaseare ignored (--pathsoverrides--changed). An empty sequence lints nothing. A root that does not exist becomes an LT999 finding (exit 2); a root outsiderepo_rootraises ValueError.changedinside a git worktree: files changed sincemerge-base(base, HEAD)plus working-tree changes (staged, unstaged, untracked), filtered to existing*.pyunderrepo_root— deleted paths are skipped. An unresolvablebaseraisesRuntimeError.- otherwise (including
changedoutside a git worktree): full scan ofrepo_root.
repo_root defaults to the git toplevel of the cwd, else the cwd.
Every mode skips the directories config/lint.yaml excludes; only
lint_files ignores them.
ratchet_exit_code¶
2 on any LT999, 1 on any exceedance, else 0 — exit_code's ratchet twin.
Callers pass compare(findings, load_baseline(path)); an absent
baseline loads as {}, making every unwaived finding an exceedance, so
this equals exit_code(findings) when no baseline file exists — the
ratchet is always on, no mode branch. LT999 dominance is unconditional:
engine errors are never baselined ("crash, never a silent pass").
render¶
Dispatch to a formatter by name — what check --format renders with.
Not the only rendering path: hook-exec calls :func:format_text
directly, because its reader is an agent and no --format choice
selects that shape.
exceedances reaches only the json format — text and github have no
place to show a ratchet delta, and silently dropping it there is the
intended behavior, not a gap.
text is read by a person, so it asks for the collapsed form; github
and json are consumed per location and emit one entry per finding.
resolve_repo_root¶
Explicit root resolved; else the git toplevel of the cwd, else the cwd.
Public so the CLI anchors lint_paths, the baseline read path, and
baseline write's destination to one root — Finding.path keys and
baseline keys must share it.