From bfa0706aed915a5ba54209fed8233a76e55aaca2 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Thu, 11 Dec 2025 22:38:31 -0500 Subject: [PATCH 1/8] feat: add GitHub issue automation with project fields and labeling - Add GitHub Actions workflow for automated issue processing - Implement project field management for GitHub Projects v2 - Add task labeller action for issue categorization - Enhance GitHub tools with optimized project operations - Add automation configuration for issue metadata --- .../actions/strands-task-labeller/action.yml | 162 +++++++++ .github/config/automation-config.json | 159 +++++++++ .github/scripts/python/agent_runner.py | 4 + .github/scripts/python/github_tools.py | 327 ++++++++++++++++++ .../workflows/issue-metadata-automation.yml | 28 ++ 5 files changed, 680 insertions(+) create mode 100644 .github/actions/strands-task-labeller/action.yml create mode 100644 .github/config/automation-config.json create mode 100644 .github/workflows/issue-metadata-automation.yml diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml new file mode 100644 index 00000000..607f7437 --- /dev/null +++ b/.github/actions/strands-task-labeller/action.yml @@ -0,0 +1,162 @@ +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:::*" + ] + }, { + "Effect": "Allow", + "Action": "s3:ListBucket", + "Resource": [ + "arn:aws:s3:::*" + ] + } + ] + } + + - 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 Metadata Automation Agent. Your role is to analyze new GitHub issues and automatically apply appropriate metadata including labels, area expert assignments, and priority classification. + +## Core Responsibilities +1. **Label Assignment**: Apply relevant labels from the available repository labels based on issue content +2. **Area Expert Tagging**: Tag appropriate team members using @mentions based on technical areas +3. **Priority Classification**: Assess and set priority levels based on impact and urgency + +## Process +1. Read the automation configuration from .github/config/automation-config.json +2. Analyze the issue title, description, and any provided context +3. Apply appropriate labels using GitHub tools +4. Tag relevant area experts with @mentions in a comment +5. Set priority level if applicable + +## Available Labels (from config) +Use ONLY these labels from the repository: dependencies, javascript, bug, documentation, duplicate, enhancement, github_actions, good first issue, help wanted, implement-task, invalid, project-task, question, review-task, wontfix + +## Priority Keywords (from config) +- **Sev2**: crash, security, data loss, production down, critical bug, high priority bug +- **On-Call**: urgent, blocking, production issue, critical bug, high priority bug +- **High**: performance, regression, breaking change, important, high customer impact, block users +- **Medium**: improvement, enhancement, feature request +- **Low**: documentation, typo, cleanup, minor + +## Area Expert Assignment Rules +- Match issue content to technical areas (typescript, bedrock, openai, mcp, etc.) +- Tag specific experts based on the area_experts configuration +- Use @username format for mentions +- Group related experts in single comment when appropriate + +## 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 +- Set Priority field using set_issue_project_field tool (Priority field only - do NOT change Status) +- Post a single comment with expert mentions that includes: + - Brief issue summary + - Priority assessment with reasoning + - Expert mentions with context: "CC @expert1 for [specific area] - [brief reason why they are needed]" + - Clear action items or next steps +- 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: Do NOT change Status field +- Only set Priority field (Sev2, On-Call, High, Medium, Low, N/A) +- Leave Status field unchanged (should remain ToDo, In Progress, etc.) +- The automation is for metadata only, not workflow management + +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 "Perform complete GitHub issue metadata automation for issue #${{ github.event.issue.number }}: analyze content, apply labels, tag area experts, and set priority based on automation configuration." diff --git a/.github/config/automation-config.json b/.github/config/automation-config.json new file mode 100644 index 00000000..2087019d --- /dev/null +++ b/.github/config/automation-config.json @@ -0,0 +1,159 @@ +{ + "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"] + }, + "constraints_engine": { + "name": "Constraints Engine", + "github_usernames": ["dbschmigelski"] + } + }, + "labels": { + "available": ["dependencies", "javascript", "bug", "documentation", "duplicate", "enhancement", "github_actions", "good first issue", "help wanted", "implement-task", "invalid", "project-task", "question", "review-task", "wontfix"] + }, + "priority_levels": ["Sev2", "On-Call", "High", "Medium", "Low", "N/A"], + "priority_rules": { + "sev2_keywords": ["crash", "security", "data loss", "production down", "critical bug","high priority bug"], + "oncall_keywords": ["urgent", "blocking", "production issue","critical bug","high priority bug"], + "high_keywords": ["performance", "regression", "breaking change", "important","high customer impact","block users"], + "medium_keywords": ["improvement", "enhancement", "feature request"], + "low_keywords": ["documentation", "typo", "cleanup", "minor"] + } +} \ No newline at end of file diff --git a/.github/scripts/python/agent_runner.py b/.github/scripts/python/agent_runner.py index db10cead..ae3dbc21 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, @@ -29,6 +30,7 @@ list_issues, list_pull_requests, reply_to_review_comment, + set_issue_project_field, update_issue, update_pull_request, ) @@ -62,6 +64,8 @@ def _get_all_tools() -> list[Any]: update_issue, list_issues, add_issue_comment, + add_labels_to_issue, + set_issue_project_field, get_issue_comments, # GitHub PR tools diff --git a/.github/scripts/python/github_tools.py b/.github/scripts/python/github_tools.py index 8826b461..baecabde 100644 --- a/.github/scripts/python/github_tools.py +++ b/.github/scripts/python/github_tools.py @@ -841,3 +841,330 @@ 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 +@check_should_call_write_api_or_record +def set_issue_project_field(issue_number: int, field_name: str, field_value: str, repo: str | None = None) -> str: + """Sets a custom field value for an issue in GitHub Projects v2. + If the issue is not in a project, it will try to add it to the first available project. + + Args: + issue_number: The issue number + field_name: The project field name (e.g., "Priority") + field_value: The field value (e.g., "Sev2", "High", etc.) + repo: GitHub repository in the format "owner/repo" (optional; falls back to env var) + + Returns: + Result of the operation + """ + if repo is None: + repo = os.environ.get("GITHUB_REPOSITORY") + if not repo: + return "Error: GITHUB_REPOSITORY environment variable not found" + + # Use PAT_TOKEN for Projects API if available, fallback to GITHUB_TOKEN + token = os.environ.get("PAT_TOKEN") or os.environ.get("GITHUB_TOKEN", "") + if not token: + return "Error: Neither PAT_TOKEN nor GITHUB_TOKEN environment variable found" + + owner, repo_name = repo.split("/") + + try: + console.print(Panel(f"Starting project field update for issue #{issue_number}, field '{field_name}' = '{field_value}'", title="[bold blue]Debug: Starting", border_style="blue")) + console.print(Panel(f"Using token: {'PAT_TOKEN' if os.environ.get('PAT_TOKEN') else 'GITHUB_TOKEN'}", title="[bold blue]Debug: Token", border_style="blue")) + + # Step 1: Get issue and available projects + query = """ + query($owner: String!, $repo: String!, $number: Int!) { + repository(owner: $owner, name: $repo) { + issue(number: $number) { + id + projectItems(first: 10) { + nodes { + id + project { + id + title + fields(first: 20) { + nodes { + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } + } + } + } + } + } + } + projectsV2(first: 10) { + nodes { + id + title + fields(first: 20) { + nodes { + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } + } + } + } + } + } + user(login: $owner) { + projectsV2(first: 10) { + nodes { + id + title + fields(first: 20) { + nodes { + ... on ProjectV2SingleSelectField { + id + name + options { + id + name + } + } + } + } + } + } + } + } + """ + + response = requests.post( + "https://api.github.com/graphql", + headers={"Authorization": f"Bearer {token}"}, + json={"query": query, "variables": {"owner": owner, "repo": repo_name, "number": issue_number}}, + timeout=30 + ) + response.raise_for_status() + data = response.json() + + console.print(Panel(f"GraphQL Response Status: {response.status_code}", title="[bold blue]Debug: Response", border_style="blue")) + + if "errors" in data: + console.print(Panel(f"GraphQL Errors: {data['errors']}", title="[bold red]Debug: GraphQL Errors", border_style="red")) + return f"GraphQL Error: {data['errors']}" + + issue_data = data["data"]["repository"]["issue"] + issue_id = issue_data["id"] + project_items = issue_data["projectItems"]["nodes"] + + # Get all available projects (repo + user) + repo_projects = data["data"]["repository"]["projectsV2"]["nodes"] + user_projects = data["data"]["user"]["projectsV2"]["nodes"] if data["data"]["user"] else [] + all_projects = repo_projects + user_projects + + # Debug logging + console.print(Panel(f"Found {len(repo_projects)} repo projects, {len(user_projects)} user projects", title="[bold blue]Debug: Projects Found", border_style="blue")) + for i, project in enumerate(all_projects): + try: + field_names = [field.get("name", "NO_NAME") for field in project.get("fields", {}).get("nodes", [])] + console.print(Panel(f"Project {i}: {project.get('title', 'NO_TITLE')} - Fields: {field_names}", title="[bold blue]Debug: Project Fields", border_style="blue")) + console.print(Panel(f"Project {i} raw data: {project}", title="[bold blue]Debug: Raw Project", border_style="blue")) + except Exception as e: + console.print(Panel(f"Error processing project {i}: {e}", title="[bold red]Debug: Project Error", border_style="red")) + + project_item_id = None + target_project = None + field_id = None + option_id = None + + # Check if issue is already in a project with the field + for item in project_items: + project = item["project"] + for field in project["fields"]["nodes"]: + if not field or "name" not in field: + continue + if field["name"] == field_name: + field_id = field["id"] + project_item_id = item["id"] + target_project = project + # Find the option ID for the field value + for option in field["options"]: + if option["name"] == field_value: + option_id = option["id"] + break + break + if field_id: + break + + # If not in a project with the field, find a suitable project and add the issue + if not project_item_id: + for project in all_projects: + for field in project["fields"]["nodes"]: + if not field or "name" not in field: + continue + if field["name"] == field_name: + target_project = project + field_id = field["id"] + # Find the option ID for the field value + for option in field["options"]: + if option["name"] == field_value: + option_id = option["id"] + break + break + if target_project: + break + + if not target_project: + return f"No project found with field '{field_name}'" + + # Add issue to project + add_mutation = """ + mutation($input: AddProjectV2ItemByIdInput!) { + addProjectV2ItemById(input: $input) { + item { + id + } + } + } + """ + + add_input = { + "projectId": target_project["id"], + "contentId": issue_id + } + + response = requests.post( + "https://api.github.com/graphql", + headers={"Authorization": f"Bearer {token}"}, + json={"query": add_mutation, "variables": {"input": add_input}}, + timeout=30 + ) + response.raise_for_status() + add_result = response.json() + + if "errors" in add_result: + return f"Error adding issue to project: {add_result['errors']}" + + project_item_id = add_result["data"]["addProjectV2ItemById"]["item"]["id"] + + # Store Status field info for later update + status_field_id = None + todo_option_id = None + for field in target_project["fields"]["nodes"]: + if not field or "name" not in field: + continue + if field["name"] == "Status": + status_field_id = field["id"] + for option in field.get("options", []): + if option.get("name") in ["Todo", "ToDo", "To Do", "Backlog"]: + todo_option_id = option["id"] + break + break + + if not option_id: + return f"Could not find option '{field_value}' for field '{field_name}'" + + # Step 2: Update the project field + update_mutation = """ + mutation($input: UpdateProjectV2ItemFieldValueInput!) { + updateProjectV2ItemFieldValue(input: $input) { + projectV2Item { + id + } + } + } + """ + + update_input = { + "projectId": target_project["id"], + "itemId": project_item_id, + "fieldId": field_id, + "value": { + "singleSelectOptionId": option_id + } + } + + response = requests.post( + "https://api.github.com/graphql", + headers={"Authorization": f"Bearer {token}"}, + json={"query": update_mutation, "variables": {"input": update_input}}, + timeout=30 + ) + response.raise_for_status() + result = response.json() + + if "errors" in result: + return f"GraphQL Mutation Error: {result['errors']}" + + # Finally, set Status to Todo at the very end + if status_field_id and todo_option_id: + status_mutation = """ + mutation($input: UpdateProjectV2ItemFieldValueInput!) { + updateProjectV2ItemFieldValue(input: $input) { + projectV2Item { + id + } + } + } + """ + + status_input = { + "projectId": target_project["id"], + "itemId": project_item_id, + "fieldId": status_field_id, + "value": { + "singleSelectOptionId": todo_option_id + } + } + + requests.post( + "https://api.github.com/graphql", + headers={"Authorization": f"Bearer {token}"}, + json={"query": status_mutation, "variables": {"input": status_input}}, + timeout=30 + ) + + message = f"Successfully set project field '{field_name}' to '{field_value}' for issue #{issue_number} in project '{target_project['title']}'" + console.print(Panel(escape(message), title="[bold green]Project Field Updated", border_style="green")) + return message + + except Exception as e: + error_msg = f"Error setting project field: {e!s}" + console.print(Panel(escape(error_msg), title="[bold red]Error", border_style="red")) + return error_msg + + +@tool +@log_inputs +def assign_area_experts(issue_number: int, repo: str | None = None) -> str: + """Assigns area experts to a PR.""" 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 }} From 928560baa0331c0df8c05d43c21bf9011c90feda Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Thu, 11 Dec 2025 22:50:52 -0500 Subject: [PATCH 2/8] fix: prevent automatic status field changes in project operations - Remove automatic Status field setting to Todo - Only update the specifically requested project field - Remove debug logging and incomplete functions --- .github/scripts/python/github_tools.py | 63 ++------------------------ 1 file changed, 3 insertions(+), 60 deletions(-) diff --git a/.github/scripts/python/github_tools.py b/.github/scripts/python/github_tools.py index baecabde..1389223a 100644 --- a/.github/scripts/python/github_tools.py +++ b/.github/scripts/python/github_tools.py @@ -895,8 +895,7 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str owner, repo_name = repo.split("/") try: - console.print(Panel(f"Starting project field update for issue #{issue_number}, field '{field_name}' = '{field_value}'", title="[bold blue]Debug: Starting", border_style="blue")) - console.print(Panel(f"Using token: {'PAT_TOKEN' if os.environ.get('PAT_TOKEN') else 'GITHUB_TOKEN'}", title="[bold blue]Debug: Token", border_style="blue")) + # Step 1: Get issue and available projects query = """ @@ -977,10 +976,7 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str response.raise_for_status() data = response.json() - console.print(Panel(f"GraphQL Response Status: {response.status_code}", title="[bold blue]Debug: Response", border_style="blue")) - if "errors" in data: - console.print(Panel(f"GraphQL Errors: {data['errors']}", title="[bold red]Debug: GraphQL Errors", border_style="red")) return f"GraphQL Error: {data['errors']}" issue_data = data["data"]["repository"]["issue"] @@ -992,15 +988,7 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str user_projects = data["data"]["user"]["projectsV2"]["nodes"] if data["data"]["user"] else [] all_projects = repo_projects + user_projects - # Debug logging - console.print(Panel(f"Found {len(repo_projects)} repo projects, {len(user_projects)} user projects", title="[bold blue]Debug: Projects Found", border_style="blue")) - for i, project in enumerate(all_projects): - try: - field_names = [field.get("name", "NO_NAME") for field in project.get("fields", {}).get("nodes", [])] - console.print(Panel(f"Project {i}: {project.get('title', 'NO_TITLE')} - Fields: {field_names}", title="[bold blue]Debug: Project Fields", border_style="blue")) - console.print(Panel(f"Project {i} raw data: {project}", title="[bold blue]Debug: Raw Project", border_style="blue")) - except Exception as e: - console.print(Panel(f"Error processing project {i}: {e}", title="[bold red]Debug: Project Error", border_style="red")) + project_item_id = None target_project = None @@ -1076,20 +1064,6 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str return f"Error adding issue to project: {add_result['errors']}" project_item_id = add_result["data"]["addProjectV2ItemById"]["item"]["id"] - - # Store Status field info for later update - status_field_id = None - todo_option_id = None - for field in target_project["fields"]["nodes"]: - if not field or "name" not in field: - continue - if field["name"] == "Status": - status_field_id = field["id"] - for option in field.get("options", []): - if option.get("name") in ["Todo", "ToDo", "To Do", "Backlog"]: - todo_option_id = option["id"] - break - break if not option_id: return f"Could not find option '{field_value}' for field '{field_name}'" @@ -1126,34 +1100,6 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str if "errors" in result: return f"GraphQL Mutation Error: {result['errors']}" - # Finally, set Status to Todo at the very end - if status_field_id and todo_option_id: - status_mutation = """ - mutation($input: UpdateProjectV2ItemFieldValueInput!) { - updateProjectV2ItemFieldValue(input: $input) { - projectV2Item { - id - } - } - } - """ - - status_input = { - "projectId": target_project["id"], - "itemId": project_item_id, - "fieldId": status_field_id, - "value": { - "singleSelectOptionId": todo_option_id - } - } - - requests.post( - "https://api.github.com/graphql", - headers={"Authorization": f"Bearer {token}"}, - json={"query": status_mutation, "variables": {"input": status_input}}, - timeout=30 - ) - message = f"Successfully set project field '{field_name}' to '{field_value}' for issue #{issue_number} in project '{target_project['title']}'" console.print(Panel(escape(message), title="[bold green]Project Field Updated", border_style="green")) return message @@ -1164,7 +1110,4 @@ def set_issue_project_field(issue_number: int, field_name: str, field_value: str return error_msg -@tool -@log_inputs -def assign_area_experts(issue_number: int, repo: str | None = None) -> str: - """Assigns area experts to a PR.""" + From 1754f1571890518dfd00eaf4ae619131aaece71e Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Thu, 11 Dec 2025 23:00:17 -0500 Subject: [PATCH 3/8] feat: limit expert tagging to 2 most relevant people - Update system prompt to tag only the 2 most related experts - Prioritize experts most closely related to specific issue content - Reduce notification noise while maintaining relevant expertise --- .github/actions/strands-task-labeller/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml index 607f7437..a24d3d61 100644 --- a/.github/actions/strands-task-labeller/action.yml +++ b/.github/actions/strands-task-labeller/action.yml @@ -125,9 +125,9 @@ Use ONLY these labels from the repository: dependencies, javascript, bug, docume ## Area Expert Assignment Rules - Match issue content to technical areas (typescript, bedrock, openai, mcp, etc.) -- Tag specific experts based on the area_experts configuration +- Tag ONLY the 2 most relevant experts based on the area_experts configuration - Use @username format for mentions -- Group related experts in single comment when appropriate +- Prioritize experts most closely related to the specific issue content ## Area Expert Matching Match issue content to these technical areas and tag corresponding experts: @@ -145,7 +145,7 @@ Match issue content to these technical areas and tag corresponding experts: - Post a single comment with expert mentions that includes: - Brief issue summary - Priority assessment with reasoning - - Expert mentions with context: "CC @expert1 for [specific area] - [brief reason why they are needed]" + - Expert mentions (MAX 2 people): "CC @expert1 @expert2 for [specific area] - [brief reason why they are needed]" - Clear action items or next steps - 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) From 79309ff87ce2f8a81e733c6aabde95d076639598 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Thu, 11 Dec 2025 23:05:06 -0500 Subject: [PATCH 4/8] fix:restrict inline policy --- .github/actions/strands-task-labeller/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml index a24d3d61..0da32fda 100644 --- a/.github/actions/strands-task-labeller/action.yml +++ b/.github/actions/strands-task-labeller/action.yml @@ -78,13 +78,13 @@ runs: "s3:DeleteObject" ], "Resource": [ - "arn:aws:s3:::*" + "arn:aws:s3:::strands-typescript-project-sessions/*", ] }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": [ - "arn:aws:s3:::*" + "arn:aws:s3:::strands-typescript-project-sessions", ] } ] From 200a3fb021747b5f8f8d2dfd3c4feb7904aeae44 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Fri, 12 Dec 2025 16:41:56 -0500 Subject: [PATCH 5/8] refactor: remove prioritization and make labels dynamic --- .../actions/strands-task-labeller/action.yml | 49 ++++++++----------- .github/config/automation-config.json | 11 ----- .github/scripts/python/agent_runner.py | 2 - 3 files changed, 21 insertions(+), 41 deletions(-) diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml index 0da32fda..eedc87a1 100644 --- a/.github/actions/strands-task-labeller/action.yml +++ b/.github/actions/strands-task-labeller/action.yml @@ -78,13 +78,13 @@ runs: "s3:DeleteObject" ], "Resource": [ - "arn:aws:s3:::strands-typescript-project-sessions/*", + "arn:aws:s3:::${{ inputs.sessions_bucket }}/*" ] }, { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": [ - "arn:aws:s3:::strands-typescript-project-sessions", + "arn:aws:s3:::${{ inputs.sessions_bucket }}" ] } ] @@ -99,29 +99,24 @@ runs: 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 Metadata Automation Agent. Your role is to analyze new GitHub issues and automatically apply appropriate metadata including labels, area expert assignments, and priority classification. + 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 available repository labels based on issue content +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. **Priority Classification**: Assess and set priority levels based on impact and urgency +3. **Issue Summary**: Generate a brief summary of the issue ## Process -1. Read the automation configuration from .github/config/automation-config.json -2. Analyze the issue title, description, and any provided context -3. Apply appropriate labels using GitHub tools -4. Tag relevant area experts with @mentions in a comment -5. Set priority level if applicable - -## Available Labels (from config) -Use ONLY these labels from the repository: dependencies, javascript, bug, documentation, duplicate, enhancement, github_actions, good first issue, help wanted, implement-task, invalid, project-task, question, review-task, wontfix - -## Priority Keywords (from config) -- **Sev2**: crash, security, data loss, production down, critical bug, high priority bug -- **On-Call**: urgent, blocking, production issue, critical bug, high priority bug -- **High**: performance, regression, breaking change, important, high customer impact, block users -- **Medium**: improvement, enhancement, feature request -- **Low**: documentation, typo, cleanup, minor +1. First, get available repository labels using GitHub API +2. Read the automation configuration from .github/config/automation-config.json for area experts +3. Analyze the issue title, description, and any provided context +4. Apply appropriate labels using GitHub tools +5. Tag relevant area experts with @mentions in a comment + +## Label Assignment +- Use list_issues or GitHub API to get available repository labels dynamically +- Apply labels that match the issue content (bug, enhancement, documentation, etc.) +- Focus on categorization labels that help with organization ## Area Expert Assignment Rules - Match issue content to technical areas (typescript, bedrock, openai, mcp, etc.) @@ -141,22 +136,20 @@ Match issue content to these technical areas and tag corresponding experts: ## Output Format - Apply labels immediately using add_labels_to_issue tool -- Set Priority field using set_issue_project_field tool (Priority field only - do NOT change Status) - Post a single comment with expert mentions that includes: - Brief issue summary - - Priority assessment with reasoning - Expert mentions (MAX 2 people): "CC @expert1 @expert2 for [specific area] - [brief reason why they are needed]" - - Clear action items or next steps + - 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: Do NOT change Status field -- Only set Priority field (Sev2, On-Call, High, Medium, Low, N/A) -- Leave Status field unchanged (should remain ToDo, In Progress, etc.) -- The automation is for metadata only, not workflow management +## 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 "Perform complete GitHub issue metadata automation for issue #${{ github.event.issue.number }}: analyze content, apply labels, tag area experts, and set priority based on automation configuration." + uv run ${{ runner.temp }}/strands-labeller/.github/scripts/python/agent_runner.py "Analyze and categorize GitHub issue #${{ github.event.issue.number }}: 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 index 2087019d..5fa64552 100644 --- a/.github/config/automation-config.json +++ b/.github/config/automation-config.json @@ -144,16 +144,5 @@ "name": "Constraints Engine", "github_usernames": ["dbschmigelski"] } - }, - "labels": { - "available": ["dependencies", "javascript", "bug", "documentation", "duplicate", "enhancement", "github_actions", "good first issue", "help wanted", "implement-task", "invalid", "project-task", "question", "review-task", "wontfix"] - }, - "priority_levels": ["Sev2", "On-Call", "High", "Medium", "Low", "N/A"], - "priority_rules": { - "sev2_keywords": ["crash", "security", "data loss", "production down", "critical bug","high priority bug"], - "oncall_keywords": ["urgent", "blocking", "production issue","critical bug","high priority bug"], - "high_keywords": ["performance", "regression", "breaking change", "important","high customer impact","block users"], - "medium_keywords": ["improvement", "enhancement", "feature request"], - "low_keywords": ["documentation", "typo", "cleanup", "minor"] } } \ No newline at end of file diff --git a/.github/scripts/python/agent_runner.py b/.github/scripts/python/agent_runner.py index ae3dbc21..e650bf3e 100644 --- a/.github/scripts/python/agent_runner.py +++ b/.github/scripts/python/agent_runner.py @@ -30,7 +30,6 @@ list_issues, list_pull_requests, reply_to_review_comment, - set_issue_project_field, update_issue, update_pull_request, ) @@ -65,7 +64,6 @@ def _get_all_tools() -> list[Any]: list_issues, add_issue_comment, add_labels_to_issue, - set_issue_project_field, get_issue_comments, # GitHub PR tools From 751d3785a29f6701e49d74c379eca526f42cada5 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Fri, 12 Dec 2025 16:44:01 -0500 Subject: [PATCH 6/8] feat: enhance issue triage with similar issue search --- .../actions/strands-task-labeller/action.yml | 24 +- .github/scripts/python/agent_runner.py | 2 + .github/scripts/python/github_tools.py | 260 +++--------------- 3 files changed, 58 insertions(+), 228 deletions(-) diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml index eedc87a1..18be222d 100644 --- a/.github/actions/strands-task-labeller/action.yml +++ b/.github/actions/strands-task-labeller/action.yml @@ -104,14 +104,21 @@ runs: ## 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 of the issue +3. **Issue Summary**: Generate a brief summary with context from similar issues ## Process -1. First, get available repository labels using GitHub API -2. Read the automation configuration from .github/config/automation-config.json for area experts -3. Analyze the issue title, description, and any provided context -4. Apply appropriate labels using GitHub tools -5. Tag relevant area experts with @mentions in a comment +1. First, search for similar issues using search_similar_issues tool to understand context +2. Get available repository labels using GitHub API +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 appropriate labels using GitHub tools +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 - Use list_issues or GitHub API to get available repository labels dynamically @@ -137,8 +144,9 @@ Match issue content to these technical areas and tag corresponding experts: ## Output Format - Apply labels immediately using add_labels_to_issue tool - Post a single comment with expert mentions that includes: - - Brief issue summary + - 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) @@ -152,4 +160,4 @@ Use GitHub tools to execute all actions. Be thorough but efficient in your analy 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 }}: apply appropriate labels and tag relevant area experts based on issue content." + 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/scripts/python/agent_runner.py b/.github/scripts/python/agent_runner.py index e650bf3e..369e6144 100644 --- a/.github/scripts/python/agent_runner.py +++ b/.github/scripts/python/agent_runner.py @@ -30,6 +30,7 @@ list_issues, list_pull_requests, reply_to_review_comment, + search_similar_issues, update_issue, update_pull_request, ) @@ -65,6 +66,7 @@ def _get_all_tools() -> list[Any]: add_issue_comment, add_labels_to_issue, get_issue_comments, + search_similar_issues, # GitHub PR tools create_pull_request, diff --git a/.github/scripts/python/github_tools.py b/.github/scripts/python/github_tools.py index 1389223a..8d51328e 100644 --- a/.github/scripts/python/github_tools.py +++ b/.github/scripts/python/github_tools.py @@ -868,244 +868,64 @@ def add_labels_to_issue(issue_number: int, labels: list[str], repo: str | None = @tool @log_inputs -@check_should_call_write_api_or_record -def set_issue_project_field(issue_number: int, field_name: str, field_value: str, repo: str | None = None) -> str: - """Sets a custom field value for an issue in GitHub Projects v2. - If the issue is not in a project, it will try to add it to the first available project. +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: - issue_number: The issue number - field_name: The project field name (e.g., "Priority") - field_value: The field value (e.g., "Sev2", "High", etc.) + 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: - Result of the operation + 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" - # Use PAT_TOKEN for Projects API if available, fallback to GITHUB_TOKEN - token = os.environ.get("PAT_TOKEN") or os.environ.get("GITHUB_TOKEN", "") + token = os.environ.get("GITHUB_TOKEN", "") if not token: - return "Error: Neither PAT_TOKEN nor GITHUB_TOKEN environment variable found" + return "Error: GITHUB_TOKEN environment variable not found" - owner, repo_name = repo.split("/") - - try: + # Build search query + search_query = f"{query} repo:{repo} is:issue" + if state != "all": + search_query += f" state:{state}" - - # Step 1: Get issue and available projects - query = """ - query($owner: String!, $repo: String!, $number: Int!) { - repository(owner: $owner, name: $repo) { - issue(number: $number) { - id - projectItems(first: 10) { - nodes { - id - project { - id - title - fields(first: 20) { - nodes { - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - } - } - projectsV2(first: 10) { - nodes { - id - title - fields(first: 20) { - nodes { - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - } - user(login: $owner) { - projectsV2(first: 10) { - nodes { - id - title - fields(first: 20) { - nodes { - ... on ProjectV2SingleSelectField { - id - name - options { - id - name - } - } - } - } - } - } - } - } - """ - - response = requests.post( - "https://api.github.com/graphql", - headers={"Authorization": f"Bearer {token}"}, - json={"query": query, "variables": {"owner": owner, "repo": repo_name, "number": issue_number}}, - timeout=30 - ) + 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 "errors" in data: - return f"GraphQL Error: {data['errors']}" - - issue_data = data["data"]["repository"]["issue"] - issue_id = issue_data["id"] - project_items = issue_data["projectItems"]["nodes"] - - # Get all available projects (repo + user) - repo_projects = data["data"]["repository"]["projectsV2"]["nodes"] - user_projects = data["data"]["user"]["projectsV2"]["nodes"] if data["data"]["user"] else [] - all_projects = repo_projects + user_projects - - - project_item_id = None - target_project = None - field_id = None - option_id = None - - # Check if issue is already in a project with the field - for item in project_items: - project = item["project"] - for field in project["fields"]["nodes"]: - if not field or "name" not in field: - continue - if field["name"] == field_name: - field_id = field["id"] - project_item_id = item["id"] - target_project = project - # Find the option ID for the field value - for option in field["options"]: - if option["name"] == field_value: - option_id = option["id"] - break - break - if field_id: - break - - # If not in a project with the field, find a suitable project and add the issue - if not project_item_id: - for project in all_projects: - for field in project["fields"]["nodes"]: - if not field or "name" not in field: - continue - if field["name"] == field_name: - target_project = project - field_id = field["id"] - # Find the option ID for the field value - for option in field["options"]: - if option["name"] == field_value: - option_id = option["id"] - break - break - if target_project: - break - - if not target_project: - return f"No project found with field '{field_name}'" - - # Add issue to project - add_mutation = """ - mutation($input: AddProjectV2ItemByIdInput!) { - addProjectV2ItemById(input: $input) { - item { - id - } - } - } - """ - - add_input = { - "projectId": target_project["id"], - "contentId": issue_id - } - - response = requests.post( - "https://api.github.com/graphql", - headers={"Authorization": f"Bearer {token}"}, - json={"query": add_mutation, "variables": {"input": add_input}}, - timeout=30 - ) - response.raise_for_status() - add_result = response.json() - - if "errors" in add_result: - return f"Error adding issue to project: {add_result['errors']}" - - project_item_id = add_result["data"]["addProjectV2ItemById"]["item"]["id"] - - if not option_id: - return f"Could not find option '{field_value}' for field '{field_name}'" - - # Step 2: Update the project field - update_mutation = """ - mutation($input: UpdateProjectV2ItemFieldValueInput!) { - updateProjectV2ItemFieldValue(input: $input) { - projectV2Item { - id - } - } - } - """ - - update_input = { - "projectId": target_project["id"], - "itemId": project_item_id, - "fieldId": field_id, - "value": { - "singleSelectOptionId": option_id - } - } - - response = requests.post( - "https://api.github.com/graphql", - headers={"Authorization": f"Bearer {token}"}, - json={"query": update_mutation, "variables": {"input": update_input}}, - timeout=30 - ) - response.raise_for_status() - result = response.json() - - if "errors" in result: - return f"GraphQL Mutation Error: {result['errors']}" - - message = f"Successfully set project field '{field_name}' to '{field_value}' for issue #{issue_number} in project '{target_project['title']}'" - console.print(Panel(escape(message), title="[bold green]Project Field Updated", border_style="green")) - return message - + 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 setting project field: {e!s}" + error_msg = f"Error searching issues: {e!s}" console.print(Panel(escape(error_msg), title="[bold red]Error", border_style="red")) return error_msg From 9ac377ce8b830a2cd6c811a419af9c3729f75bf6 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Fri, 12 Dec 2025 16:47:42 -0500 Subject: [PATCH 7/8] fix: enforce using only existing repository labels --- .../actions/strands-task-labeller/action.yml | 10 ++++--- .github/scripts/python/agent_runner.py | 2 ++ .github/scripts/python/github_tools.py | 29 +++++++++++++++++++ 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/.github/actions/strands-task-labeller/action.yml b/.github/actions/strands-task-labeller/action.yml index 18be222d..ebea8d39 100644 --- a/.github/actions/strands-task-labeller/action.yml +++ b/.github/actions/strands-task-labeller/action.yml @@ -108,10 +108,10 @@ runs: ## Process 1. First, search for similar issues using search_similar_issues tool to understand context -2. Get available repository labels using GitHub API +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 appropriate labels using GitHub tools +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 @@ -121,8 +121,10 @@ runs: - Reference similar issues in your summary if relevant ## Label Assignment -- Use list_issues or GitHub API to get available repository labels dynamically -- Apply labels that match the issue content (bug, enhancement, documentation, etc.) +- 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 diff --git a/.github/scripts/python/agent_runner.py b/.github/scripts/python/agent_runner.py index 369e6144..a3393d92 100644 --- a/.github/scripts/python/agent_runner.py +++ b/.github/scripts/python/agent_runner.py @@ -29,6 +29,7 @@ get_pr_review_and_comments, list_issues, list_pull_requests, + list_repository_labels, reply_to_review_comment, search_similar_issues, update_issue, @@ -67,6 +68,7 @@ def _get_all_tools() -> list[Any]: 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 8d51328e..090d016f 100644 --- a/.github/scripts/python/github_tools.py +++ b/.github/scripts/python/github_tools.py @@ -930,4 +930,33 @@ def search_similar_issues(query: str, state: str = "all", limit: int = 5, repo: 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 + + From 7a6ba3008a6796a06dda9d697eed6e3302e3fa24 Mon Sep 17 00:00:00 2001 From: Jack Yuan Date: Fri, 12 Dec 2025 16:48:03 -0500 Subject: [PATCH 8/8] Update automation-config.json --- .github/config/automation-config.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/config/automation-config.json b/.github/config/automation-config.json index 5fa64552..8399f4f9 100644 --- a/.github/config/automation-config.json +++ b/.github/config/automation-config.json @@ -140,9 +140,9 @@ "name": "TypeScript", "github_usernames": ["Unshure", "zastrowm", "afarntrog", "awsarron"] }, - "constraints_engine": { - "name": "Constraints Engine", + "steering": { + "name": "Steering", "github_usernames": ["dbschmigelski"] } } -} \ No newline at end of file +}