(base: str)
| 139 | |
| 140 | |
| 141 | def run_diff(base: str) -> int: |
| 142 | ruff = _ruff_cmd() |
| 143 | if ruff is None: |
| 144 | print("ruff_lint: ruff not found on PATH — skipping (CI installs ruff). OK.") |
| 145 | return 0 |
| 146 | merge_base, files = _changed_py_files(base) |
| 147 | if not files: |
| 148 | print(f"ruff_lint: no changed .py files vs {base} ({merge_base[:12]}). OK.") |
| 149 | return 0 |
| 150 | |
| 151 | all_findings = _ruff_check_json(ruff, files) |
| 152 | # Build the added-line map once per file, intersect. |
| 153 | added_map = {f: _added_lines(merge_base, f) for f in files} |
| 154 | new_findings = [] |
| 155 | for finding in all_findings: |
| 156 | rel = os.path.relpath(finding.get("filename", ""), REPO_ROOT) |
| 157 | row = finding.get("location", {}).get("row") |
| 158 | if rel in added_map and row in added_map[rel]: |
| 159 | new_findings.append(finding) |
| 160 | |
| 161 | print( |
| 162 | f"ruff_lint (diff vs {base}): {len(files)} changed .py file(s), " |
| 163 | f"{len(all_findings)} total finding(s) in them, " |
| 164 | f"{len(new_findings)} on added/modified lines." |
| 165 | ) |
| 166 | if new_findings: |
| 167 | print("\nNew ruff violations introduced by this change:\n") |
| 168 | _print_findings(new_findings) |
| 169 | print( |
| 170 | "\nThe curated ruff gate (E9+F+B) flags NEW code only. Fix the lines above, " |
| 171 | "or — if a finding is a genuine false positive — add a scoped " |
| 172 | "`# noqa: <CODE>` with a one-line reason. Config: pyproject.toml [tool.ruff]." |
| 173 | ) |
| 174 | return 1 |
| 175 | print("ruff_lint: no new violations on added/modified lines. OK.") |
| 176 | return 0 |
| 177 | |
| 178 | |
| 179 | def run_all(strict: bool) -> int: |
no test coverage detected