Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def filter_pdf_pages(pdf_path: Path, pages: list[int], output_path: Path) -> Pat
import fitz # noqa: PLC0415 — PyMuPDF
except ImportError:
logger.error(
"PyMuPDF is required for slide filtering. Install via: pip install pymupdf"
"PyMuPDF is required for slide filtering. Install via: uv pip install pymupdf"
)
sys.exit(EXIT_FAILURE)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def export_pdf_to_svg(
import fitz # noqa: F401, PLC0415 — PyMuPDF availability check
except ImportError as e:
raise PyMuPDFError(
"PyMuPDF is required for SVG export. Install via: pip install pymupdf"
"PyMuPDF is required for SVG export. Install via: uv pip install pymupdf"
) from e

try:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def render_pages(
try:
import fitz # noqa: F401, PLC0415 — PyMuPDF availability check
except ImportError:
logger.error("PyMuPDF is required. Install via: pip install pymupdf")
logger.error("PyMuPDF is required. Install via: uv pip install pymupdf")
sys.exit(EXIT_FAILURE)

output_dir.mkdir(parents=True, exist_ok=True)
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/beval.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ jobs:
npm ci --prefix evals/beval
echo "${{ github.workspace }}/evals/beval/node_modules/.bin" >> "$GITHUB_PATH"

- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9

- name: Install beval
# beval is hosted under a personal account (vyta) while an org-owned
# home is evaluated. The install is pinned to a specific commit SHA to
# mitigate supply-chain risk in the interim.
run: pip install --no-cache-dir "beval[all] @ git+https://github.com/vyta/beval.git@d9f46c24f03b0b806d928a8a8ce2fc66d8e470fb#subdirectory=python"
run: uv pip install --system --no-cache-dir "beval[all] @ git+https://github.com/vyta/beval.git@d9f46c24f03b0b806d928a8a8ce2fc66d8e470fb#subdirectory=python"

- name: Start agent (TCP)
env:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/pip-install-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Pip Install Lint

on:
workflow_call:

jobs:
check-bare-pip-install:
name: Check for bare pip install
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd

- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97
with:
python-version: '3.x'

- name: Run bare pip install lint check
run: python scripts/lint_pip_install.py
4 changes: 4 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jobs:
with:
soft-fail: false

pip-install-lint:
name: Pip Install Lint
uses: ./.github/workflows/pip-install-lint.yml

markdown-lint:
name: Markdown Lint
uses: ./.github/workflows/markdown-lint.yml
Expand Down
82 changes: 82 additions & 0 deletions scripts/lint_pip_install.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Copyright (c) 2026 Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: MIT

"""
Lint script to detect bare 'pip install' calls.
The repository follows a 'uv-first' Python convention.
"""
import os
import re
import sys


EXCLUDE_DIRS = {".git", "evals", ".venv", "venv", "env", "node_modules", "__pycache__"}
EXCLUDE_FILES = {"THIRD-PARTY-NOTICES", "lint_pip_install.py"}
TARGET_DIRS = [".github/workflows", "scripts"]

bare_pip_pattern = re.compile(r'\bpip3?\s+install\b')
uv_pip_pattern = re.compile(r'\buv\s+pip3?\s+install\b')

violations = []
scanned_files = set()

def should_exclude(filepath):
norm_path = filepath.replace("\\", "/")

parts = set(norm_path.split("/"))
if parts & EXCLUDE_DIRS:
return True

for ex_file in EXCLUDE_FILES:
if ex_file in norm_path:
return True

return False

def scan_file(filepath):
if filepath in scanned_files:
return
scanned_files.add(filepath)

if should_exclude(filepath):
return

try:
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
for i, line in enumerate(f, 1):
stripped_line = line.strip()

if not stripped_line or stripped_line.startswith("#"):
continue

if stripped_line.startswith("name:") or stripped_line.startswith("- name:"):
continue

if bare_pip_pattern.search(line) and not uv_pip_pattern.search(line):
violations.append(f"{filepath}:{i}: {stripped_line}")
except Exception as e:
print(f"Warning: Could not read {filepath}: {e}")

def main():
for target_dir in TARGET_DIRS:
if os.path.isdir(target_dir):
for root, _, files in os.walk(target_dir):
for file in files:
scan_file(os.path.join(root, file))

for root, _, files in os.walk("."):
for file in files:
if file.endswith(".py"):
scan_file(os.path.join(root, file))

if violations:
print("ERROR: Found bare 'pip install' calls. Use 'uv pip install' instead.")
print("The repo follows a uv-first Python convention.\n")
for v in sorted(set(violations)):
print(f" - {v}")
sys.exit(1)
else:
print("Success: No bare 'pip install' calls found.")

if __name__ == "__main__":
main()