diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml new file mode 100644 index 00000000..ebea8d39 --- /dev/null +++ b/.github/actions/strands-task-labeller/action.yml @@ -0,0 +1,165 @@ +name: 'Strands Task Labeller' +description: 'Automatically analyze and label GitHub issues using Strands agent' +inputs: + aws_role_arn: + description: 'AWS IAM role ARN for authentication' + required: true + sessions_bucket: + description: 'S3 bucket for session storage' + required: true + pat_token: + description: 'pat token to modify repo' + required: false + +runs: + using: 'composite' + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + sparse-checkout: | + .github + + - name: Copy .github to safe directory + shell: bash + run: | + mkdir -p ${{ runner.temp }}/strands-labeller + cp -r .github ${{ runner.temp }}/strands-labeller + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install uv + uses: astral-sh/setup-uv@v3 + with: + enable-cache: true + cache-dependency-glob: '${{ runner.temp }}/strands-labeller/.github/scripts/python/requirements.txt' + + - name: Install Strands Agents + shell: bash + run: | + echo "📦 Installing from requirements.txt" + uv pip install --system -r ${{ runner.temp }}/strands-labeller/.github/scripts/python/requirements.txt --quiet + + - name: Configure Git + shell: bash + run: | + git config --global user.name "Strands Agent" + git config --global user.email "217235299+strands-agent@users.noreply.github.com" + git config --global core.pager cat + PAGER=cat + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ inputs.aws_role_arn }} + role-session-name: GitHubActions-StrandsAgent-${{ github.run_id }} + aws-region: us-west-2 + mask-aws-account-id: true + inline-session-policy: >- + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid":"BedrockAccess", + "Effect": "Allow", + "Action": [ + "bedrock:InvokeModelWithResponseStream", + "bedrock:InvokeModel" + ], + "Resource": "*" + }, { + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::${{ inputs.sessions_bucket }}/*" + ] + }, { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [ + "arn:aws:s3:::${{ inputs.sessions_bucket }}" + ] + } + ] + } + + - name: Execute labelling task + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + PAT_TOKEN: ${{ inputs.pat_token }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_WRITE: 'true' + S3_SESSION_BUCKET: ${{ inputs.sessions_bucket }} + SESSION_ID: 'labeller-${{ github.event.issue.number }}-${{ github.run_id }}' + INPUT_SYSTEM_PROMPT: 'You are a GitHub Issue Categorization Agent. Your role is to analyze new GitHub issues and automatically apply appropriate labels and tag relevant area experts. + +## Core Responsibilities +1. **Label Assignment**: Apply relevant labels from the repository based on issue content +2. **Area Expert Tagging**: Tag appropriate team members using @mentions based on technical areas +3. **Issue Summary**: Generate a brief summary with context from similar issues + +## Process +1. First, search for similar issues using search_similar_issues tool to understand context +2. Get available repository labels using list_repository_labels tool +3. Read the automation configuration from .github/config/automation-config.json for area experts +4. Analyze the issue title, description, and any provided context +5. Apply ONLY existing labels from step 2 using add_labels_to_issue tool +6. Tag relevant area experts with @mentions in a comment + +## Similar Issues Analysis +- Use search_similar_issues tool to find related issues before categorizing +- Extract keywords from the new issue title and description +- Review similar issues to understand patterns and common solutions +- Reference similar issues in your summary if relevant + +## Label Assignment +- CRITICAL: Use list_repository_labels tool FIRST to get available labels +- ONLY apply labels that exist in the repository - NEVER create new labels +- Match issue content to existing labels (bug, enhancement, documentation, etc.) +- If no perfect match exists, choose the closest existing label +- Focus on categorization labels that help with organization + +## Area Expert Assignment Rules +- Match issue content to technical areas (typescript, bedrock, openai, mcp, etc.) +- Tag ONLY the 2 most relevant experts based on the area_experts configuration +- Use @username format for mentions +- Prioritize experts most closely related to the specific issue content + +## Area Expert Matching +Match issue content to these technical areas and tag corresponding experts: +- agent_loop, context_management, async_streaming, tool_executors +- bi_directional_streaming, human_in_loop, hooks +- bedrock, anthropic, gemini, litellm, llamaapi, llama_cpp, mistralai, ollama, openai, sagemaker, writer, cohere +- multi_agent, a2a, sessions, memory, mcp, structured_outputs +- agentcore_integrations, telemetry, evaluations, github_workflows +- website_docs, tools, samples, agent_builder, mcp_server +- embodied_ai, typescript, constraints_engine + +## Output Format +- Apply labels immediately using add_labels_to_issue tool +- Post a single comment with expert mentions that includes: + - Brief issue summary with context from similar issues (if found) + - Expert mentions (MAX 2 people): "CC @expert1 @expert2 for [specific area] - [brief reason why they are needed]" + - Links to similar issues if relevant + - Clear action items or next steps if applicable +- Keep comments professional but informative +- For TypeScript issues: Only tag TypeScript experts if the issue is specifically about TypeScript compilation, syntax, or type errors (not just because this is a TypeScript project) + +## Important Notes +- Do NOT set any project fields or priorities - this agent is for categorization only +- Focus on helping organize and route issues to the right people +- Prioritization will be handled by a separate process + +Use GitHub tools to execute all actions. Be thorough but efficient in your analysis.' + STRANDS_TOOL_CONSOLE_MODE: 'enabled' + BYPASS_TOOL_CONSENT: 'true' + run: | + uv run ${{ runner.temp }}/strands-labeller/.github/scripts/python/agent_runner.py "Analyze and categorize GitHub issue #${{ github.event.issue.number }}: search for similar issues, apply appropriate labels, and tag relevant area experts based on issue content." diff --git a/.github/config/automation-config.json b/.github/config/automation-config.json new file mode 100644 index 00000000..8399f4f9 --- /dev/null +++ b/.github/config/automation-config.json @@ -0,0 +1,148 @@ +{ + "area_experts": { + "agent_loop": { + "name": "Agent Loop", + "github_usernames": ["pgrayy", "zastrowm"] + }, + "context_management": { + "name": "Context Management / Windows", + "github_usernames": ["Unshure", "afarntrog"] + }, + "async_streaming": { + "name": "Async / Callbacks Streaming", + "github_usernames": ["pgrayy"] + }, + "tool_executors": { + "name": "Tool Executors", + "github_usernames": ["pgrayy"] + }, + "bi_directional_streaming": { + "name": "Bi-directional Streaming", + "github_usernames": ["mehtarac", "mkmeral", "pgrayy"] + }, + "human_in_loop": { + "name": "Human in the Loop / Interrupts", + "github_usernames": ["mkmeral", "pgrayy"] + }, + "hooks": { + "name": "Hooks", + "github_usernames": ["zastrowm"] + }, + "bedrock": { + "name": "Bedrock Model Provider", + "github_usernames": ["mehtarac"] + }, + "anthropic": { + "name": "Anthropic Model Provider", + "github_usernames": ["pgrayy"] + }, + "gemini": { + "name": "Gemini Model Provider", + "github_usernames": ["notgitika"] + }, + "litellm": { + "name": "LiteLLM Model Provider", + "github_usernames": ["dbschmigelski"] + }, + "llamaapi": { + "name": "LlamaAPI Model Provider", + "github_usernames": ["pgrayy"] + }, + "llama_cpp": { + "name": "llama.cpp Model Provider", + "github_usernames": ["awsarron"] + }, + "mistralai": { + "name": "MistralAI Model Provider", + "github_usernames": ["mehtarac"] + }, + "ollama": { + "name": "Ollama Model Provider", + "github_usernames": ["dbschmigelski"] + }, + "openai": { + "name": "OpenAI Model Provider", + "github_usernames": ["pgrayy"] + }, + "sagemaker": { + "name": "SageMaker Model Provider", + "github_usernames": ["mehtarac"] + }, + "writer": { + "name": "Writer Model Provider", + "github_usernames": ["mehtarac"] + }, + "cohere": { + "name": "Cohere Model Provider", + "github_usernames": ["Unshure"] + }, + "multi_agent": { + "name": "Multi-agent Orchestrators", + "github_usernames": ["mkmeral", "JackYPCOnline", "awsarron"] + }, + "a2a": { + "name": "A2A", + "github_usernames": ["awsarron"] + }, + "sessions": { + "name": "Sessions", + "github_usernames": ["Unshure", "JackYPCOnline"] + }, + "memory": { + "name": "Memory", + "github_usernames": ["Unshure", "mehtarac", "afarntrog"] + }, + "mcp": { + "name": "MCP", + "github_usernames": ["dbschmigelski"] + }, + "structured_outputs": { + "name": "Structured Outputs", + "github_usernames": ["afarntrog"] + }, + "agentcore_integrations": { + "name": "AgentCore Integrations", + "github_usernames": ["JackYPCOnline", "afarntrog", "mehtarac", "dbschmigelski"] + }, + "telemetry": { + "name": "Telemetry / Observability", + "github_usernames": ["poshinchen", "JackYPCOnline"] + }, + "evaluations": { + "name": "Evaluations", + "github_usernames": ["poshinchen"] + }, + "github_workflows": { + "name": "GitHub Workflows and Actions", + "github_usernames": ["dbschmigelski", "yonib05", "Unshure"] + }, + "website_docs": { + "name": "Website and Docs", + "github_usernames": ["zastrowm", "yonib05", "rycolez"] + }, + "tools": { + "name": "Tools", + "github_usernames": ["cagataycali", "JackYPCOnline", "zastrowm", "mehtarac"] + }, + "agent_builder": { + "name": "Agent Builder", + "github_usernames": ["cagataycali"] + }, + "mcp_server": { + "name": "MCP Server", + "github_usernames": ["JackYPCOnline", "mkmeral"] + }, + "embodied_ai": { + "name": "Embodied / Physical AI and Edge Inference / Robotics", + "github_usernames": ["awsarron", "cagataycali", "mkmeral"] + }, + "typescript": { + "name": "TypeScript", + "github_usernames": ["Unshure", "zastrowm", "afarntrog", "awsarron"] + }, + "steering": { + "name": "Steering", + "github_usernames": ["dbschmigelski"] + } + } +} diff --git a/.github/scripts/python/agent_runner.py b/.github/scripts/python/agent_runner.py index db10cead..a3393d92 100644 --- a/.github/scripts/python/agent_runner.py +++ b/.github/scripts/python/agent_runner.py @@ -20,6 +20,7 @@ # Import local GitHub tools we need from github_tools import ( add_issue_comment, + add_labels_to_issue, create_issue, create_pull_request, get_issue, @@ -28,7 +29,9 @@ get_pr_review_and_comments, list_issues, list_pull_requests, + list_repository_labels, reply_to_review_comment, + search_similar_issues, update_issue, update_pull_request, ) @@ -62,7 +65,10 @@ def _get_all_tools() -> list[Any]: update_issue, list_issues, add_issue_comment, + add_labels_to_issue, get_issue_comments, + search_similar_issues, + list_repository_labels, # GitHub PR tools create_pull_request, diff --git a/.github/scripts/python/github_tools.py b/.github/scripts/python/github_tools.py index 8826b461..090d016f 100644 --- a/.github/scripts/python/github_tools.py +++ b/.github/scripts/python/github_tools.py @@ -841,3 +841,122 @@ def get_pr_review_and_comments(pr_number: int, show_resolved: bool = False, repo error_msg = f"Error: {e!s}\n\nStack trace:\n{traceback.format_exc()}" console.print(Panel(escape(error_msg), title="[bold red]Error", border_style="red")) return error_msg + +@tool +@log_inputs +@check_should_call_write_api_or_record +def add_labels_to_issue(issue_number: int, labels: list[str], repo: str | None = None) -> str: + """Adds labels to an issue. + + Args: + issue_number: The issue number + labels: List of label names to add + repo: GitHub repository in the format "owner/repo" (optional; falls back to env var) + + Returns: + Result of the operation + """ + result = _github_request("POST", f"issues/{issue_number}/labels", repo, {"labels": labels}) + if isinstance(result, str): + console.print(Panel(escape(result), title="[bold red]Error", border_style="red")) + return result + + message = f"Labels {labels} added to issue #{issue_number}" + console.print(Panel(escape(message), title="[bold green]Success", border_style="green")) + return message + + +@tool +@log_inputs +def search_similar_issues(query: str, state: str = "all", limit: int = 5, repo: str | None = None) -> str: + """Searches for similar issues using GitHub's search API. + + Args: + query: Search query (keywords from issue title/body) + state: Filter by state: "open", "closed", or "all" (default: "all") + limit: Maximum number of results to return (default: 5) + repo: GitHub repository in the format "owner/repo" (optional; falls back to env var) + + Returns: + List of similar issues with relevance scores + """ + if repo is None: + repo = os.environ.get("GITHUB_REPOSITORY") + if not repo: + return "Error: GITHUB_REPOSITORY environment variable not found" + + token = os.environ.get("GITHUB_TOKEN", "") + if not token: + return "Error: GITHUB_TOKEN environment variable not found" + + # Build search query + search_query = f"{query} repo:{repo} is:issue" + if state != "all": + search_query += f" state:{state}" + + url = "https://api.github.com/search/issues" + headers = { + "Authorization": f"Bearer {token}", + "Accept": "application/vnd.github.v3+json", + } + params = {"q": search_query, "per_page": limit, "sort": "relevance"} + + try: + response = requests.get(url, headers=headers, params=params, timeout=30) + response.raise_for_status() + data = response.json() + + if data["total_count"] == 0: + message = f"No similar issues found for query: {query}" + console.print(Panel(escape(message), title="[bold yellow]Info", border_style="yellow")) + return message + + output = f"Found {data['total_count']} similar issue(s) (showing top {min(limit, len(data['items']))}):\n\n" + for issue in data["items"]: + output += f"#{issue['number']} - {issue['title']}\n" + output += f" State: {issue['state']} | Author: {issue['user']['login']}\n" + output += f" URL: {issue['html_url']}\n" + if issue.get("labels"): + labels = ", ".join([label["name"] for label in issue["labels"]]) + output += f" Labels: {labels}\n" + output += "\n" + + console.print(Panel(escape(output), title="[bold green]🔍 Similar Issues", border_style="blue")) + return output + + except Exception as e: + error_msg = f"Error searching issues: {e!s}" + console.print(Panel(escape(error_msg), title="[bold red]Error", border_style="red")) + return error_msg + + +@tool +@log_inputs +def list_repository_labels(repo: str | None = None) -> str: + """Lists all available labels in the repository. + + Args: + repo: GitHub repository in the format "owner/repo" (optional; falls back to env var) + + Returns: + List of available label names + """ + result = _github_request("GET", "labels", repo) + if isinstance(result, str): + console.print(Panel(escape(result), title="[bold red]Error", border_style="red")) + return result + + if not result: + message = f"No labels found in {repo or os.environ.get('GITHUB_REPOSITORY')}" + console.print(Panel(escape(message), title="[bold yellow]Info", border_style="yellow")) + return message + + label_names = [label["name"] for label in result] + output = f"Available labels in {repo or os.environ.get('GITHUB_REPOSITORY')}:\n" + output += ", ".join(label_names) + + console.print(Panel(escape(output), title="[bold green]🏷️ Repository Labels", border_style="blue")) + return output + + + diff --git a/.github/workflows/issue-metadata-automation.yml b/.github/workflows/issue-metadata-automation.yml new file mode 100644 index 00000000..1a12f23d --- /dev/null +++ b/.github/workflows/issue-metadata-automation.yml @@ -0,0 +1,28 @@ +name: Issue Metadata Automation + +on: + issues: + types: [opened,reopened] +permissions: + contents: write + issues: write + pull-requests: write + repository-projects: write +jobs: + auto-label: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + id-token: write + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Auto-label and tag experts + uses: ./.github/actions/strands-task-labeller + with: + aws_role_arn: ${{ secrets.AWS_ROLE_ARN }} + sessions_bucket: ${{ secrets.S3_SESSION_BUCKET }} + pat_token: ${{ secrets.PAT_TOKEN }}