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
12 changes: 8 additions & 4 deletions .github/agents/data-science/test-streamlit-dashboard.agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Confirm prerequisites and prepare the test environment.
2. Verify Playwright and pytest-playwright are installed. Install if missing:

```bash
pip install playwright pytest-playwright pytest-asyncio
uv pip install playwright pytest-playwright pytest-asyncio
playwright install chromium
```

Expand Down Expand Up @@ -107,9 +107,13 @@ async def test_page_navigation(page):
"""Test sidebar navigation functionality"""
await page.goto("http://localhost:8501")

pages = ["📊 Summary Statistics", "📈 Univariate Analysis",
"🔗 Multivariate Analysis", "⏰ Time Series Analysis",
"💬 Chat Interface"]
pages = [
"📊 Summary Statistics",
"📈 Univariate Analysis",
"🔗 Multivariate Analysis",
"⏰ Time Series Analysis",
"💬 Chat Interface",
]

for page_name in pages:
await page.select_option("select", page_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Conventions for Python 3.11+ scripts used in automation, tooling, and CLI applic

## Environment and Dependency Management

1. **Never use `pip install` directly.** All package management must be handled using `uv` (e.g., `uv add <package>`).
1. **Never use `pip install` directly.** All package management must be handled using `uv` (e.g., `uv add <package>`). <!-- pip-install-ok -->
2. **Never run Python scripts or tools outside a virtual environment.** Always execute scripts via `uv run <script.py>` or ensure the
`.venv` is activated before running.
3. Ensure a `.venv` exists in the project root before executing any Python code. If starting from scratch, refer to the `uv-projects.instructions.md` file for environment setup.
Expand All @@ -21,7 +21,7 @@ import sys

EXIT_SUCCESS = 0 # Successful execution
EXIT_FAILURE = 1 # General failure
EXIT_ERROR = 2 # Arguments or configuration error
EXIT_ERROR = 2 # Arguments or configuration error


def main() -> int:
Expand Down Expand Up @@ -121,13 +121,17 @@ import os
from pathlib import Path


def run_command(cmd: list[str], cwd: Path | None = None, extra_env: dict[str, str] | None = None) -> str:
def run_command(
cmd: list[str], cwd: Path | None = None, extra_env: dict[str, str] | None = None
) -> str:
"""Run command and return stdout, raising on failure."""
env = os.environ.copy()
if extra_env:
env.update(extra_env)
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True, cwd=cwd, env=env)
result = subprocess.run(
cmd, capture_output=True, text=True, check=True, cwd=cwd, env=env
)
return result.stdout
except subprocess.CalledProcessError as e:
logger.error("Command failed: %s\nstderr: %s", e.returncode, e.stderr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ You are a Python environment specialist focused on uv virtual environment manage

## Strict Constraints

1. **Never use `pip install` directly.** Always use `uv add <package>` for all package management (adding, removing, locking, and syncing dependencies).
1. **Never use `pip install` directly.** Always use `uv add <package>` for all package management (adding, removing, locking, and syncing dependencies). <!-- pip-install-ok -->
2. **Never run Python scripts or tools outside a virtual environment.** Always execute scripts via `uv run <script.py>` or ensure the `.venv` is activated first.
3. **Verify `.venv` existence:** Before any Python work, verify a `.venv` exists in the project root. If not, create one with `uv init` and `uv sync`.
4. **Migration Path:** If a `requirements.txt` exists but no `pyproject.toml`, migrate it by running `uv init` and then `uv add -r requirements.txt`.
Expand Down
2 changes: 1 addition & 1 deletion .github/instructions/experimental/pptx.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Include `<!-- markdownlint-disable-file -->` at the top of all markdown files cr
* For update and cleanup workflows, preserve existing masters and layouts from the source deck.
* When updating an existing deck, always regenerate from content YAML rather than modifying the PPTX directly; update content files first, then regenerate into `slide-deck/`.
* Follow the repo's Python environment conventions (`uv-projects.instructions.md`) for virtual environment and dependency management.
* All dependencies are declared in `pyproject.toml` at the skill root. The `Invoke-PptxPipeline.ps1` orchestrator manages the virtual environment automatically. Never install packages with `pip install` directly.
* All dependencies are declared in `pyproject.toml` at the skill root. The `Invoke-PptxPipeline.ps1` orchestrator manages the virtual environment automatically. Never install packages with `pip install` directly. <!-- pip-install-ok -->
* When scripts fail due to missing modules or import errors, follow the Environment Recovery steps in the `powerpoint` skill instructions.

### Build Mode: `--template` vs `--source`
Expand Down
Comment thread
PratikWayase marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,9 @@ day = np.random.choice(pd.date_range(start=start_date, end=end_date))
day = pd.Timestamp(day).date() # Ensures Python datetime.date
hour = int(np.random.choice(range(8, 19)))
minute = int(np.random.randint(0, 60))
start_time = datetime.combine(day, datetime.min.time()) + timedelta(hours=hour, minutes=minute)
start_time = datetime.combine(day, datetime.min.time()) + timedelta(
hours=hour, minutes=minute
)
```

### Data Types & Ranges
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ Use the bash script instead. Verify the bash script flags by sending `invoke-ppt

- Use `send_to_terminal` to send commands to the active terminal
- Use `get_terminal_output` to poll for completion
- Do not run `pip install` or manual dependency installation
- Do not run `pip install` or manual dependency installation <!-- pip-install-ok -->
- Rely on PowerPoint skill environment setup (`uv sync`) and documented prerequisites
- Keep output under the project slug render directory

Expand Down
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
28 changes: 28 additions & 0 deletions .github/workflows/pip-install-lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Pip Install Lint
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed

on:
workflow_call:
Comment thread
PratikWayase marked this conversation as resolved.
inputs:
soft-fail:
description: 'Whether to continue on bare pip install violations' # pip-install-ok
required: false
type: boolean
default: false

permissions:
contents: read

jobs:
check-bare-pip-install:
name: Check for bare pip install
runs-on: ubuntu-latest
permissions:
contents: read
continue-on-error: ${{ inputs.soft-fail }}
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Comment thread
PratikWayase marked this conversation as resolved.
with:
persist-credentials: false
- name: Run bare pip install lint check
run: pwsh -File scripts/linting/Invoke-PipInstallLint.ps1
9 changes: 9 additions & 0 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ jobs:
with:
soft-fail: false

pip-install-lint:
name: Pip Install Lint
uses: ./.github/workflows/pip-install-lint.yml
permissions:
contents: read
with:
soft-fail: false

markdown-lint:
name: Markdown Lint
uses: ./.github/workflows/markdown-lint.yml
Expand Down Expand Up @@ -485,6 +493,7 @@ jobs:
- node-tests
- fuzz-tests
- pip-audit
- pip-install-lint
- docusaurus-tests
- frontmatter-validation
- adr-consistency-validation
Expand Down
138 changes: 138 additions & 0 deletions scripts/linting/Invoke-PipInstallLint.ps1
Comment thread
PratikWayase marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Copyright (c) 2026 Microsoft Corporation. All rights reserved.
# SPDX-License-Identifier: MIT

<#
.SYNOPSIS
Lint script to detect bare 'pip install' calls.
The repository follows a 'uv-first' Python convention.
#>

param(
[string]$TestDirectory = "."
)

$ErrorActionPreference = "Stop"

$script:ExcludeDirs = @(".git", "evals", ".venv", "venv", "env", "node_modules", "__pycache__")
$script:ExcludeFiles = @("THIRD-PARTY-NOTICES", "Invoke-PipInstallLint.ps1", "Invoke-PipInstallLint.Tests.ps1")
$script:Violations = @()
$script:ScannedFiles = @{}

function script:Test-ExcludedPath {
param([string]$Path)
$normalizedPath = $Path.Replace("\", "/").ToLowerInvariant()

foreach ($dir in $script:ExcludeDirs) {
if ($dir -eq "evals") {
if ($normalizedPath -match "(^|/)evals(/|$)" -and $normalizedPath -notmatch "(^|/)scripts/evals(/|$)") {
return $true
}
} else {
if ($normalizedPath -match "(^|/)$([regex]::Escape($dir))(/|$)") {
return $true
}
}
}

foreach ($file in $script:ExcludeFiles) {
if ($normalizedPath -match "(^|/)$([regex]::Escape($file))(/|$)") {
return $true
}
}
return $false
}

function script:Invoke-FileScan {
param([string]$FilePath)

$normalizedPath = $FilePath.Replace("\", "/")
if ($script:ScannedFiles.ContainsKey($normalizedPath)) { return }
$script:ScannedFiles[$normalizedPath] = $true

if (script:Test-ExcludedPath -Path $FilePath) { return }

$ext = [System.IO.Path]::GetExtension($FilePath).ToLowerInvariant()

if ($ext -notin @(".py", ".ps1", ".yml", ".yaml", ".md", ".sh", "")) { return }

try {
$lines = Get-Content -Path $FilePath -Raw -ErrorAction SilentlyContinue
if (-not $lines) { return }

$lineNumber = 1
foreach ($line in $lines -split "`r?`n") {
$strippedLine = $line.Trim()

if ([string]::IsNullOrWhiteSpace($strippedLine)) {
$lineNumber++
continue
}

if ($line -match "#\s*pip-install-ok\b" -or $line -match "<!--\s*pip-install-ok\s*-->") {
$lineNumber++
continue
}

$cleanedLine = $line -replace "\buv\s+pip3?\s+install\b", "" -replace "%pip\s+install", ""
if ($cleanedLine -match "\bpip3?\s+install\b") {
if ($strippedLine -notmatch "^(name:|- name:)") {
$script:Violations += "$FilePath`:$lineNumber`: $strippedLine"
}
}
$lineNumber++
}
}
catch {
Write-Warning "Could not read $FilePath`: $_"
}
}

function script:Invoke-Lint {
param([string]$TargetDir = ".")

$script:Violations = @()
$script:ScannedFiles = @{}

$extensions = @("*.py", "*.ps1", "*.yml", "*.yaml", "*.md", "*.sh")

if ($TargetDir -eq ".") {
$rootDir = (Resolve-Path ".").Path

$trackedFiles = git -C $rootDir ls-files 2>$null

if ($LASTEXITCODE -eq 0 -and $trackedFiles) {
foreach ($file in $trackedFiles) {
$ext = [System.IO.Path]::GetExtension($file)
if ($ext -in @(".py", ".ps1", ".yml", ".yaml", ".md", ".sh")) {
$fullPath = Join-Path $rootDir $file
script:Invoke-FileScan -FilePath $fullPath
}
}
} else {
Get-ChildItem -Path $rootDir -Recurse -Include $extensions -File -Force -ErrorAction SilentlyContinue | ForEach-Object {
script:Invoke-FileScan -FilePath $_.FullName
}
}
} else {
Get-ChildItem -Path $TargetDir -Recurse -Include $extensions -File -Force -ErrorAction SilentlyContinue | ForEach-Object {
script:Invoke-FileScan -FilePath $_.FullName
}
}

if ($script:Violations.Count -gt 0) {
Write-Error "ERROR: Found bare 'pip install' calls. Use 'uv pip install' instead."
Write-Host "The repo follows a uv-first Python convention.`n" -ForegroundColor Yellow
foreach ($v in ($script:Violations | Sort-Object -Unique)) {
Write-Host " - $v" -ForegroundColor Red
}
return $false
} else {
Write-Host "Success: No bare 'pip install' calls found." -ForegroundColor Green
return $true
}
}

if ($MyInvocation.InvocationName -ne '.') {
$success = script:Invoke-Lint -TargetDir $TestDirectory
if (-not $success) { exit 1 }
}
Loading