Files
awesome-copilot/cookbook/copilot-sdk/python/pr-visualization.md
James Montemagno 33f544c71d Align Copilot SDK documentation with permission handling requirements (#1107)
* Apply permission handler requirements across Copilot SDK docs

Co-authored-by: jamesmontemagno <1676321+jamesmontemagno@users.noreply.github.com>
Agent-Logs-Url: https://github.com/jamesmontemagno/awesome-copilot/sessions/adf27a88-92f8-4ca6-b3fe-1204e3bb9963

* Polish permission update formatting in SDK examples

Co-authored-by: jamesmontemagno <1676321+jamesmontemagno@users.noreply.github.com>
Agent-Logs-Url: https://github.com/jamesmontemagno/awesome-copilot/sessions/adf27a88-92f8-4ca6-b3fe-1204e3bb9963

* Fix review comments on SDK permission handling PR

Address 5 review comments from PR #1103:

1. Fix invalid object literal syntax (stray comma) in resumeSession
   example in copilot-sdk-nodejs.instructions.md

2. Replace unused PermissionHandler import with actual usage in
   cookbook/copilot-sdk/python/recipe/ralph_loop.py (was using
   inline lambda instead)

3. Replace unused approveAll import with actual usage in
   cookbook/copilot-sdk/nodejs/recipe/ralph-loop.ts (was using
   inline handler instead)

4. Add missing PermissionHandler import to 4 Python code snippets
   in skills/copilot-sdk/SKILL.md that reference it without importing

5. Add missing approveAll import to 3 TypeScript code snippets
   in skills/copilot-sdk/SKILL.md that reference it without importing

* Refactor session creation to improve code formatting and consistency across SDK examples

* Fix formatting: split multi-property lines and put closing braces on own lines

Address review comments on PR #1107:
- Split OnPermissionRequest + Model onto separate lines in Go, C#, TypeScript
- Put closing }); on its own line consistently across all examples
- Fix indentation in SKILL.md Quick Start, CLI URL, Error Handling sections
- Fix cookbook Go multiple-sessions and error-handling formatting
- Fix ralph-loop.md TypeScript indentation

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jamesmontemagno <1676321+jamesmontemagno@users.noreply.github.com>
2026-03-23 11:11:19 +11:00

232 lines
7.0 KiB
Markdown

# Generating PR Age Charts
Build an interactive CLI tool that visualizes pull request age distribution for a GitHub repository using Copilot's built-in capabilities.
> **Runnable example:** [recipe/pr_visualization.py](recipe/pr_visualization.py)
>
> ```bash
> cd recipe && pip install -r requirements.txt
> # Auto-detect from current git repo
> python pr_visualization.py
>
> # Specify a repo explicitly
> python pr_visualization.py --repo github/copilot-sdk
> ```
## Example scenario
You want to understand how long PRs have been open in a repository. This tool detects the current Git repo or accepts a repo as input, then lets Copilot fetch PR data via the GitHub MCP Server and generate a chart image.
## Prerequisites
```bash
pip install github-copilot-sdk
```
## Usage
```bash
# Auto-detect from current git repo
python pr_visualization.py
# Specify a repo explicitly
python pr_visualization.py --repo github/copilot-sdk
```
## Full example: pr_visualization.py
```python
#!/usr/bin/env python3
import asyncio
import subprocess
import sys
import os
import re
from copilot import (
CopilotClient,
SessionConfig,
MessageOptions,
SessionEvent,
PermissionHandler,
)
# ============================================================================
# Git & GitHub Detection
# ============================================================================
def is_git_repo():
try:
subprocess.run(
["git", "rev-parse", "--git-dir"],
check=True,
capture_output=True
)
return True
except (subprocess.CalledProcessError, FileNotFoundError):
return False
def get_github_remote():
try:
result = subprocess.run(
["git", "remote", "get-url", "origin"],
check=True,
capture_output=True,
text=True
)
remote_url = result.stdout.strip()
# Handle SSH: git@github.com:owner/repo.git
ssh_match = re.search(r"git@github\.com:(.+/.+?)(?:\.git)?$", remote_url)
if ssh_match:
return ssh_match.group(1)
# Handle HTTPS: https://github.com/owner/repo.git
https_match = re.search(r"https://github\.com/(.+/.+?)(?:\.git)?$", remote_url)
if https_match:
return https_match.group(1)
return None
except (subprocess.CalledProcessError, FileNotFoundError):
return None
def parse_args():
args = sys.argv[1:]
if "--repo" in args:
idx = args.index("--repo")
if idx + 1 < len(args):
return {"repo": args[idx + 1]}
return {}
def prompt_for_repo():
return input("Enter GitHub repo (owner/repo): ").strip()
# ============================================================================
# Main Application
# ============================================================================
async def main():
print("🔍 PR Age Chart Generator\n")
# Determine the repository
args = parse_args()
repo = None
if "repo" in args:
repo = args["repo"]
print(f"📦 Using specified repo: {repo}")
elif is_git_repo():
detected = get_github_remote()
if detected:
repo = detected
print(f"📦 Detected GitHub repo: {repo}")
else:
print("⚠️ Git repo found but no GitHub remote detected.")
repo = prompt_for_repo()
else:
print("📁 Not in a git repository.")
repo = prompt_for_repo()
if not repo or "/" not in repo:
print("❌ Invalid repo format. Expected: owner/repo")
sys.exit(1)
owner, repo_name = repo.split("/", 1)
# Create Copilot client
client = CopilotClient()
await client.start()
session = await client.create_session(SessionConfig(
model="gpt-5",
system_message={
"content": f"""
<context>
You are analyzing pull requests for the GitHub repository: {owner}/{repo_name}
The current working directory is: {os.getcwd()}
</context>
<instructions>
- Use the GitHub MCP Server tools to fetch PR data
- Use your file and code execution tools to generate charts
- Save any generated images to the current working directory
- Be concise in your responses
</instructions>
"""
},
on_permission_request=PermissionHandler.approve_all))
done = asyncio.Event()
# Set up event handling
def handle_event(event: SessionEvent):
if event.type.value == "assistant.message":
print(f"\n🤖 {event.data.content}\n")
elif event.type.value == "tool.execution_start":
print(f" ⚙️ {event.data.tool_name}")
elif event.type.value == "session.idle":
done.set()
session.on(handle_event)
# Initial prompt - let Copilot figure out the details
print("\n📊 Starting analysis...\n")
await session.send(MessageOptions(prompt=f"""
Fetch the open pull requests for {owner}/{repo_name} from the last week.
Calculate the age of each PR in days.
Then generate a bar chart image showing the distribution of PR ages
(group them into sensible buckets like <1 day, 1-3 days, etc.).
Save the chart as "pr-age-chart.png" in the current directory.
Finally, summarize the PR health - average age, oldest PR, and how many might be considered stale.
"""))
await done.wait()
# Interactive loop
print("\n💡 Ask follow-up questions or type \"exit\" to quit.\n")
print("Examples:")
print(" - \"Expand to the last month\"")
print(" - \"Show me the 5 oldest PRs\"")
print(" - \"Generate a pie chart instead\"")
print(" - \"Group by author instead of age\"")
print()
while True:
user_input = input("You: ").strip()
if user_input.lower() in ["exit", "quit"]:
print("👋 Goodbye!")
break
if user_input:
done.clear()
await session.send(MessageOptions(prompt=user_input))
await done.wait()
await session.destroy()
await client.stop()
if __name__ == "__main__":
asyncio.run(main())
```
## How it works
1. **Repository detection**: Checks `--repo` flag → git remote → prompts user
2. **No custom tools**: Relies entirely on Copilot CLI's built-in capabilities:
- **GitHub MCP Server** - Fetches PR data from GitHub
- **File tools** - Saves generated chart images
- **Code execution** - Generates charts using Python/matplotlib or other methods
3. **Interactive session**: After initial analysis, user can ask for adjustments
## Why this approach?
| Aspect | Custom Tools | Built-in Copilot |
| --------------- | ----------------- | --------------------------------- |
| Code complexity | High | **Minimal** |
| Maintenance | You maintain | **Copilot maintains** |
| Flexibility | Fixed logic | **AI decides best approach** |
| Chart types | What you coded | **Any type Copilot can generate** |
| Data grouping | Hardcoded buckets | **Intelligent grouping** |