Files
agent-framework/python/scripts/run_tasks_in_changed_packages.py
Eduard van Valkenburg 8ad66637d8 Python: Fix prek runner duplication and add skills (#3791)
* Python: fix prek runner running fmt/lint in all packages on core change

When a core package file changed, run_tasks_in_changed_packages.py ran
fmt, lint, and pyright in ALL 22 packages (66 tasks). Only type-checking
tasks (pyright, mypy) need to propagate to all packages since type
changes in core affect downstream packages. File-local tasks (fmt, lint)
only need to run in packages with actual file changes.

This reduces a core-only change from 66 tasks to 24 tasks (2 local +
22 pyright).

Also adds no-commit-to-branch builtin hook to protect the main branch
from direct commits.

* Python: add agent skills extracted from AGENTS.md and coding standards

Add 5 skills to python/.github/skills/ following the Agent Skills format:
- python-development: coding standards, type annotations, docstrings, logging
- python-testing: test structure, fixtures, running tests, async mode
- python-code-quality: linting, formatting, type checking, prek hooks, CI
- python-package-management: monorepo structure, lazy loading, versioning
- python-samples: sample structure, PEP 723, documentation guidelines

* Python: deduplicate AGENTS.md and instructions with agent skills

* updated skills

* fixes from review

* Python: increase timeout for web search integration test
2026-02-10 12:13:38 +00:00

100 lines
3.9 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
"""Run task(s) only in packages that have changed files, in parallel by default."""
import argparse
from pathlib import Path
from rich import print
from task_runner import build_work_items, discover_projects, run_tasks
# Tasks that need to run in all packages when core changes (type info propagates)
TYPE_CHECK_TASKS = {"pyright", "mypy"}
def get_changed_packages(
projects: list[Path], changed_files: list[str], workspace_root: Path
) -> tuple[set[Path], bool]:
"""Determine which packages have changed files.
Returns:
A tuple of (changed_packages, core_package_changed).
"""
changed_packages: set[Path] = set()
core_package_changed = False
for file_path in changed_files:
# Strip 'python/' prefix if present (when git diff is run from repo root)
file_path_str = str(file_path)
if file_path_str.startswith("python/"):
file_path_str = file_path_str[7:] # Remove 'python/' prefix
# Convert to absolute path if relative
abs_path = Path(file_path_str)
if not abs_path.is_absolute():
abs_path = workspace_root / file_path_str
# Check which package this file belongs to
for project in projects:
project_abs = workspace_root / project
try:
# Check if the file is within this project directory
abs_path.relative_to(project_abs)
changed_packages.add(project)
if project == Path("packages/core"):
core_package_changed = True
break
except ValueError:
continue
return changed_packages, core_package_changed
def main() -> None:
parser = argparse.ArgumentParser(description="Run task(s) in changed packages, in parallel by default.")
parser.add_argument("tasks", nargs="+", help="Task name(s) to run")
parser.add_argument("--files", nargs="*", default=None, help="Changed files to determine which packages to run")
parser.add_argument("--seq", action="store_true", help="Run sequentially instead of in parallel")
args = parser.parse_args()
pyproject_file = Path(__file__).parent.parent / "pyproject.toml"
workspace_root = pyproject_file.parent
projects = discover_projects(pyproject_file)
# Determine which packages to check
if not args.files or args.files == ["."]:
task_list = ", ".join(args.tasks)
print(f"[yellow]No specific files provided, running {task_list} in all packages[/yellow]")
work_items = build_work_items(sorted(set(projects)), args.tasks)
else:
changed_packages, core_changed = get_changed_packages(projects, args.files, workspace_root)
if not changed_packages:
print("[yellow]No changes detected in any package, skipping[/yellow]")
return
print(f"[cyan]Detected changes in packages: {', '.join(str(p) for p in sorted(changed_packages))}[/cyan]")
# File-local tasks (fmt, lint) only run in packages with actual changes.
# Type-checking tasks (pyright, mypy) run in all packages when core changes,
# because type changes in core propagate to downstream packages.
local_tasks = [t for t in args.tasks if t not in TYPE_CHECK_TASKS]
type_tasks = [t for t in args.tasks if t in TYPE_CHECK_TASKS]
work_items = build_work_items(sorted(changed_packages), local_tasks)
if type_tasks:
if core_changed:
print("[yellow]Core package changed - type-checking all packages[/yellow]")
work_items += build_work_items(sorted(set(projects)), type_tasks)
else:
work_items += build_work_items(sorted(changed_packages), type_tasks)
if not work_items:
print("[yellow]No matching tasks found in any package[/yellow]")
return
run_tasks(work_items, workspace_root, sequential=args.seq)
if __name__ == "__main__":
main()