From b766f2564113ce4b66ac648afd89d15d7e404b67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:39:49 +0000 Subject: [PATCH 01/14] Initial plan From 5f3924d238fa6df4eac7f07e8817b4e2d20196e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:44:48 +0000 Subject: [PATCH 02/14] Initial plan for updating smoke workflows to interact with discussions Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/code-simplifier.lock.yml | 117 +++++++++------------ 1 file changed, 50 insertions(+), 67 deletions(-) diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index e96a36cc15..420aab3450 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -87,7 +87,7 @@ jobs: GH_AW_ASSETS_BRANCH: "" GH_AW_ASSETS_MAX_SIZE_KB: 0 GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_SAFE_OUTPUTS: /tmp/gh-aw/safeoutputs/outputs.jsonl + GH_AW_SAFE_OUTPUTS: /opt/gh-aw/safeoutputs/outputs.jsonl GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json outputs: @@ -143,19 +143,7 @@ jobs: env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI - run: | - # Download official Copilot CLI installer script - curl -fsSL https://raw.githubusercontent.com/github/copilot-cli/main/install.sh -o /tmp/copilot-install.sh - - # Execute the installer with the specified version - # Pass VERSION directly to sudo to ensure it's available to the installer script - sudo VERSION=0.0.388 bash /tmp/copilot-install.sh - - # Cleanup - rm -f /tmp/copilot-install.sh - - # Verify installation - copilot --version + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.389 - name: Install awf binary run: bash /opt/gh-aw/actions/install_awf_binary.sh v0.10.0 - name: Determine automatic lockdown mode for GitHub MCP server @@ -169,7 +157,7 @@ jobs: const determineAutomaticLockdown = require('/opt/gh-aw/actions/determine_automatic_lockdown.cjs'); await determineAutomaticLockdown(github, context, core); - name: Download container images - run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/github-mcp-server:v0.29.0 ghcr.io/githubnext/gh-aw-mcpg:v0.0.74 node:lts-alpine + run: bash /opt/gh-aw/actions/download_docker_images.sh ghcr.io/github/github-mcp-server:v0.29.0 ghcr.io/githubnext/gh-aw-mcpg:v0.0.76 node:lts-alpine - name: Write Safe Outputs Config run: | mkdir -p /opt/gh-aw/safeoutputs @@ -350,10 +338,49 @@ jobs: } } EOF + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + API_KEY="" + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + PORT=3001 + + # Register API key as secret to mask it from logs + echo "::add-mask::${API_KEY}" + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: /opt/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: /opt/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash /opt/gh-aw/actions/start_safe_outputs_server.sh + - name: Start MCP gateway id: start-mcp-gateway env: GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | @@ -370,7 +397,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.74' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh @@ -387,42 +414,10 @@ jobs: } }, "safeoutputs": { - "type": "stdio", - "container": "node:lts-alpine", - "entrypoint": "node", - "entrypointArgs": ["/opt/gh-aw/safeoutputs/mcp-server.cjs"], - "mounts": ["/opt/gh-aw:/opt/gh-aw:ro", "/tmp/gh-aw:/tmp/gh-aw:rw", "${{ github.workspace }}:${{ github.workspace }}:rw"], - "env": { - "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", - "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", - "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", - "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", - "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", - "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", - "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", - "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", - "GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}", - "GITHUB_SHA": "\${GITHUB_SHA}", - "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", - "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", - "GITHUB_RUN_ID": "\${GITHUB_RUN_ID}", - "GITHUB_RUN_NUMBER": "\${GITHUB_RUN_NUMBER}", - "GITHUB_RUN_ATTEMPT": "\${GITHUB_RUN_ATTEMPT}", - "GITHUB_JOB": "\${GITHUB_JOB}", - "GITHUB_ACTION": "\${GITHUB_ACTION}", - "GITHUB_EVENT_NAME": "\${GITHUB_EVENT_NAME}", - "GITHUB_EVENT_PATH": "\${GITHUB_EVENT_PATH}", - "GITHUB_ACTOR": "\${GITHUB_ACTOR}", - "GITHUB_ACTOR_ID": "\${GITHUB_ACTOR_ID}", - "GITHUB_TRIGGERING_ACTOR": "\${GITHUB_TRIGGERING_ACTOR}", - "GITHUB_WORKFLOW": "\${GITHUB_WORKFLOW}", - "GITHUB_WORKFLOW_REF": "\${GITHUB_WORKFLOW_REF}", - "GITHUB_WORKFLOW_SHA": "\${GITHUB_WORKFLOW_SHA}", - "GITHUB_REF": "\${GITHUB_REF}", - "GITHUB_REF_NAME": "\${GITHUB_REF_NAME}", - "GITHUB_REF_TYPE": "\${GITHUB_REF_TYPE}", - "GITHUB_HEAD_REF": "\${GITHUB_HEAD_REF}", - "GITHUB_BASE_REF": "\${GITHUB_BASE_REF}" + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" } } }, @@ -445,7 +440,7 @@ jobs: engine_name: "GitHub Copilot CLI", model: process.env.GH_AW_MODEL_AGENT_COPILOT || "", version: "", - agent_version: "0.0.388", + agent_version: "0.0.389", workflow_name: "Code Simplifier", experimental: false, supports_tools_allowlist: true, @@ -463,7 +458,7 @@ jobs: allowed_domains: [], firewall_enabled: true, awf_version: "v0.10.0", - awmg_version: "v0.0.74", + awmg_version: "v0.0.76", steps: { firewall: "squid" }, @@ -1379,19 +1374,7 @@ jobs: env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Install GitHub Copilot CLI - run: | - # Download official Copilot CLI installer script - curl -fsSL https://raw.githubusercontent.com/github/copilot-cli/main/install.sh -o /tmp/copilot-install.sh - - # Execute the installer with the specified version - # Pass VERSION directly to sudo to ensure it's available to the installer script - sudo VERSION=0.0.388 bash /tmp/copilot-install.sh - - # Cleanup - rm -f /tmp/copilot-install.sh - - # Verify installation - copilot --version + run: /opt/gh-aw/actions/install_copilot_cli.sh 0.0.389 - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): From 27060c91af20623e464a617ff507b47840fae537 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:56:15 +0000 Subject: [PATCH 03/14] Add discussion comment support to all smoke workflows - Added github-discussion-query safe-input to all smoke workflows - Added discussions: read permission to all smoke workflows - Added discussion: true flag to add-comment safe-output - Updated compiler to include discussion flag in handler config - Updated workflow prompts to interact with random discussions - Each smoke workflow now picks a random discussion and adds a fun thematic comment Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 344 ++++++++++++++++++- .github/workflows/smoke-claude.md | 6 + .github/workflows/smoke-codex.lock.yml | 344 ++++++++++++++++++- .github/workflows/smoke-codex.md | 6 + .github/workflows/smoke-copilot.lock.yml | 344 ++++++++++++++++++- .github/workflows/smoke-copilot.md | 6 + .github/workflows/smoke-opencode.lock.yml | 344 ++++++++++++++++++- .github/workflows/smoke-opencode.md | 6 + pkg/workflow/compiler_safe_outputs_config.go | 4 + 9 files changed, 1396 insertions(+), 8 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index ead963124f..1720201bfa 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/mcp-pagination.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -103,6 +104,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + discussions: read issues: read pull-requests: read env: @@ -532,6 +534,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -568,6 +656,251 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of discussion objects", + "item_fields": { + "number": "integer - Discussion number", + "title": "string - Discussion title", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "body": "string - Discussion body content", + "category": "object - Category info with name field", + "labels": "array - Array of label objects with name field", + "comments": "object - Comments info with totalCount field", + "answer": "object|null - Accepted answer if exists", + "url": "string - Discussion URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get discussion numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get discussions by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get discussions in category", "query": ".[] | select(.category.name == \"Ideas\")"}, + {"description": "Get answered discussions", "query": ".[] | select(.answer != null)"}, + {"description": "Get unanswered discussions", "query": ".[] | select(.answer == null) | {number, title, category: .category.name}"}, + {"description": "Count by category", "query": "group_by(.category.name) | map({category: .[0].category.name, count: length})"} + ] + } + EOF + fi + + EOFSH_github-discussion-query + chmod +x /opt/gh-aw/safe-inputs/github-discussion-query.sh + cat > /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -595,6 +928,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -612,6 +946,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -629,7 +964,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="claude" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -1044,6 +1379,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Claude Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1057,6 +1394,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) + 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) ## Output @@ -1073,6 +1411,8 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL + 3. Add a **fun comic-book style comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and use comic-book language) + If all tests pass, add the label `smoke-claude` to the pull request. PROMPT_EOF @@ -1737,7 +2077,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 6fe2aa7980..99d4b0eb60 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -11,6 +11,7 @@ permissions: contents: read issues: read pull-requests: read + discussions: read name: Smoke Claude engine: @@ -22,6 +23,7 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -49,6 +51,7 @@ runtimes: safe-outputs: add-comment: hide-older-comments: true + discussion: true create-issue: expires: 2h group: true @@ -76,6 +79,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-${{ github.run_id }}.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) +8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) ## Output @@ -92,4 +96,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL +3. Add a **fun comic-book style comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and use comic-book language) + If all tests pass, add the label `smoke-claude` to the pull request. diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index a3c8f2f16e..3ca47d933c 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -102,6 +103,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + discussions: read issues: read pull-requests: read env: @@ -559,6 +561,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -595,6 +683,251 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of discussion objects", + "item_fields": { + "number": "integer - Discussion number", + "title": "string - Discussion title", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "body": "string - Discussion body content", + "category": "object - Category info with name field", + "labels": "array - Array of label objects with name field", + "comments": "object - Comments info with totalCount field", + "answer": "object|null - Accepted answer if exists", + "url": "string - Discussion URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get discussion numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get discussions by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get discussions in category", "query": ".[] | select(.category.name == \"Ideas\")"}, + {"description": "Get answered discussions", "query": ".[] | select(.answer != null)"}, + {"description": "Get unanswered discussions", "query": ".[] | select(.answer == null) | {number, title, category: .category.name}"}, + {"description": "Count by category", "query": "group_by(.category.name) | map({category: .[0].category.name, count: length})"} + ] + } + EOF + fi + + EOFSH_github-discussion-query + chmod +x /opt/gh-aw/safe-inputs/github-discussion-query.sh + cat > /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -622,6 +955,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -639,6 +973,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -656,7 +991,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="codex" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat > /tmp/gh-aw/mcp-config/config.toml << EOF [history] @@ -1026,6 +1361,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Codex Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1039,6 +1376,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) + 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a mystical, oracle-themed comment stating that the smoke test agent was here (be creative and use mystical language) ## Output @@ -1047,6 +1385,8 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL + Add a **mystical oracle-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use mystical language) + If all tests pass, add the label `smoke-codex` to the pull request. PROMPT_EOF @@ -1592,7 +1932,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index b6c21ab088..fb60ecace3 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -11,6 +11,7 @@ permissions: contents: read issues: read pull-requests: read + discussions: read name: Smoke Codex engine: codex strict: true @@ -18,6 +19,7 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -44,6 +46,7 @@ sandbox: safe-outputs: add-comment: hide-older-comments: true + discussion: true create-issue: expires: 2h close-older-issues: true @@ -73,6 +76,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-${{ github.run_id }}.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) +8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a mystical, oracle-themed comment stating that the smoke test agent was here (be creative and use mystical language) ## Output @@ -81,4 +85,6 @@ Add a **very brief** comment (max 5-10 lines) to the current pull request with: - ✅ or ❌ for each test result - Overall status: PASS or FAIL +Add a **mystical oracle-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use mystical language) + If all tests pass, add the label `smoke-codex` to the pull request. diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 69356f38d0..565ac65d7e 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/reporting.md name: "Smoke Copilot" @@ -102,6 +103,7 @@ jobs: permissions: actions: read contents: read + discussions: read issues: read pull-requests: read env: @@ -549,6 +551,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -585,6 +673,251 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of discussion objects", + "item_fields": { + "number": "integer - Discussion number", + "title": "string - Discussion title", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "body": "string - Discussion body content", + "category": "object - Category info with name field", + "labels": "array - Array of label objects with name field", + "comments": "object - Comments info with totalCount field", + "answer": "object|null - Accepted answer if exists", + "url": "string - Discussion URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get discussion numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get discussions by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get discussions in category", "query": ".[] | select(.category.name == \"Ideas\")"}, + {"description": "Get answered discussions", "query": ".[] | select(.answer != null)"}, + {"description": "Get unanswered discussions", "query": ".[] | select(.answer == null) | {number, title, category: .category.name}"}, + {"description": "Count by category", "query": "group_by(.category.name) | map({category: .[0].category.name, count: length})"} + ] + } + EOF + fi + + EOFSH_github-discussion-query + chmod +x /opt/gh-aw/safe-inputs/github-discussion-query.sh + cat > /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -612,6 +945,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -629,6 +963,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -646,7 +981,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh @@ -935,6 +1270,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Copilot Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -947,6 +1284,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) + 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment) ## Output @@ -965,6 +1303,8 @@ jobs: - Overall status: PASS or FAIL - Mention the pull request author and any assignees + 3. Add a **fun and creative comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and entertaining) + If all tests pass: - Add the label `smoke-copilot` to the pull request - Remove the label `smoke` from the pull request @@ -1541,7 +1881,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index 07a2d35b82..e05c9ab47f 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -11,12 +11,14 @@ permissions: contents: read pull-requests: read issues: read + discussions: read actions: read name: Smoke Copilot engine: copilot imports: - shared/gh.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -46,6 +48,7 @@ sandbox: safe-outputs: add-comment: hide-older-comments: true + discussion: true create-issue: expires: 2h group: true @@ -76,6 +79,7 @@ strict: true 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-${{ github.run_id }}.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) +7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment) ## Output @@ -94,6 +98,8 @@ strict: true - Overall status: PASS or FAIL - Mention the pull request author and any assignees +3. Add a **fun and creative comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and entertaining) + If all tests pass: - Add the label `smoke-copilot` to the pull request - Remove the label `smoke` from the pull request diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index c7484d24f7..a95576f6c6 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/opencode.md name: "Smoke OpenCode" @@ -101,6 +102,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: read + discussions: read issues: read pull-requests: read env: @@ -505,6 +507,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -541,6 +629,251 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of discussion objects", + "item_fields": { + "number": "integer - Discussion number", + "title": "string - Discussion title", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "body": "string - Discussion body content", + "category": "object - Category info with name field", + "labels": "array - Array of label objects with name field", + "comments": "object - Comments info with totalCount field", + "answer": "object|null - Accepted answer if exists", + "url": "string - Discussion URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get discussion numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get discussions by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get discussions in category", "query": ".[] | select(.category.name == \"Ideas\")"}, + {"description": "Get answered discussions", "query": ".[] | select(.answer != null)"}, + {"description": "Get unanswered discussions", "query": ".[] | select(.answer == null) | {number, title, category: .category.name}"}, + {"description": "Count by category", "query": "group_by(.category.name) | map({category: .[0].category.name, count: length})"} + ] + } + EOF + fi + + EOFSH_github-discussion-query + chmod +x /opt/gh-aw/safe-inputs/github-discussion-query.sh + cat > /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -568,6 +901,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -585,6 +919,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | @@ -601,7 +936,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="custom" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -774,6 +1109,8 @@ jobs: + + # Smoke Test: OpenCode Custom Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -786,6 +1123,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) + 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment stating that the smoke test agent was here (be creative and use space mission language) ## Output @@ -802,6 +1140,8 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL + 3. Add a **space/rocket-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use space mission language) + If all tests pass, add the label `smoke-opencode` to the pull request. PROMPT_EOF @@ -1443,7 +1783,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-opencode.md b/.github/workflows/smoke-opencode.md index f24ee5508f..3cfa9f73d2 100644 --- a/.github/workflows/smoke-opencode.md +++ b/.github/workflows/smoke-opencode.md @@ -11,11 +11,13 @@ permissions: contents: read issues: read pull-requests: read + discussions: read name: Smoke OpenCode imports: - shared/opencode.md - shared/gh.md + - shared/github-queries-safe-input.md strict: true sandbox: mcp: @@ -36,6 +38,7 @@ tools: safe-outputs: add-comment: hide-older-comments: true + discussion: true create-issue: expires: 2h group: true @@ -62,6 +65,7 @@ timeout-minutes: 10 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-${{ github.run_id }}.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) +7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment stating that the smoke test agent was here (be creative and use space mission language) ## Output @@ -78,4 +82,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL +3. Add a **space/rocket-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use space mission language) + If all tests pass, add the label `smoke-opencode` to the pull request. diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index e492c21f77..3680e5c089 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -81,6 +81,10 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow if len(cfg.AllowedRepos) > 0 { handlerConfig["allowed_repos"] = cfg.AllowedRepos } + // Add discussion flag to config + if cfg.Discussion != nil && *cfg.Discussion { + handlerConfig["discussion"] = true + } config["add_comment"] = handlerConfig } From fdc6ad458218eebb7e440a4c6d7734d15092a0a5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 18:09:49 +0000 Subject: [PATCH 04/14] Add changeset [skip-ci] --- ...ch-add-discussion-interaction-smoke-workflows.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .changeset/patch-add-discussion-interaction-smoke-workflows.md diff --git a/.changeset/patch-add-discussion-interaction-smoke-workflows.md b/.changeset/patch-add-discussion-interaction-smoke-workflows.md new file mode 100644 index 0000000000..439a4e0e1e --- /dev/null +++ b/.changeset/patch-add-discussion-interaction-smoke-workflows.md @@ -0,0 +1,13 @@ +--- +"gh-aw": patch +--- + +Add discussion interaction to smoke workflows and serialize the discussion +flag in safe-outputs handler config. + +Smoke workflows now select a random discussion and post thematic comments to +validate discussion comment functionality. The compiler now emits the +`"discussion": true` flag in `GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG` when a +workflow requests discussion output, and lock files include `discussions: write` +permission where applicable. + From 9a35946aff56ad6d4b47089eb22eb8c1960ca199 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:49:15 -0800 Subject: [PATCH 05/14] Fix github-discussion-query to use GraphQL API instead of non-existent gh discussion command (#11371) * Initial plan * Debug discussion-comment task failure - identify root cause Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Fix github-discussion-query tool to use GraphQL API Replace gh discussion list (unsupported) with gh api graphql. The gh CLI in GitHub Actions doesn't have a 'discussion' subcommand, so we use the GraphQL API to query discussions instead. - Parse repo owner/name from INPUT_REPO or GitHub context - Build GraphQL query for discussions with proper fields - Transform GraphQL output to match expected format - Maintains backward compatibility with existing jq filters Fixes discussion-comment task failure in smoke tests. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Recompile workflows using github-discussion-query tool Recompile all workflows that import shared/github-queries-safe-input.md to apply the GraphQL API fix for discussion queries. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Fix inline github-discussion-query in daily-performance-summary The daily-performance-summary.md workflow had an inline definition of github-discussion-query that overrides the shared import. Updated the inline definition to also use GraphQL API. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-fact.lock.yml | 2 +- .../daily-performance-summary.lock.yml | 72 +++++++++++++++++-- .../workflows/daily-performance-summary.md | 72 +++++++++++++++++-- .github/workflows/daily-regulatory.lock.yml | 72 +++++++++++++++++-- .../shared/github-queries-safe-input.md | 72 +++++++++++++++++-- .github/workflows/smoke-claude.lock.yml | 72 +++++++++++++++++-- .github/workflows/smoke-codex.lock.yml | 72 +++++++++++++++++-- .github/workflows/smoke-copilot.lock.yml | 72 +++++++++++++++++-- .github/workflows/smoke-opencode.lock.yml | 72 +++++++++++++++++-- 9 files changed, 529 insertions(+), 49 deletions(-) diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 7b9ab9e671..1f391ecb4e 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -1052,7 +1052,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"4750\"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"max\":1,\"target\":\"4750\"},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index f081540eb4..0d187a71f4 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -621,16 +621,76 @@ jobs: LIMIT="${INPUT_LIMIT:-30}" JQ_FILTER="${INPUT_JQ:-}" - # JSON fields to fetch - JSON_FIELDS="number,title,author,createdAt,updatedAt,body,category,labels,comments,answer,url" - - # Build and execute gh command + # Parse repository owner and name if [[ -n "$REPO" ]]; then - OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + OWNER=$(echo "$REPO" | cut -d'/' -f1) + NAME=$(echo "$REPO" | cut -d'/' -f2) else - OUTPUT=$(gh discussion list --limit "$LIMIT" --json "$JSON_FIELDS") + # Get current repository from GitHub context + OWNER="${GITHUB_REPOSITORY_OWNER:-}" + NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) + fi + + # Validate owner and name + if [[ -z "$OWNER" || -z "$NAME" ]]; then + echo "Error: Could not determine repository owner and name" >&2 + exit 1 fi + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 fi + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 fi + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 fi + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat <&2 + exit 1 fi + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat < Date: Thu, 22 Jan 2026 19:35:29 +0000 Subject: [PATCH 06/14] Add changeset [skip-ci] --- .../patch-add-discussion-interaction-to-smoke-workflows.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/patch-add-discussion-interaction-to-smoke-workflows.md diff --git a/.changeset/patch-add-discussion-interaction-to-smoke-workflows.md b/.changeset/patch-add-discussion-interaction-to-smoke-workflows.md new file mode 100644 index 0000000000..b601ac5e49 --- /dev/null +++ b/.changeset/patch-add-discussion-interaction-to-smoke-workflows.md @@ -0,0 +1,7 @@ +--- +"gh-aw": patch +--- + +Add discussion interaction to smoke workflows; compiler now serializes the `discussion` flag into the safe-outputs handler config so workflows can post comments to discussions. Lock files include `discussions: write` where applicable. + +Smoke workflows pick a random discussion and post a thematic comment (copilot: playful, claude: comic-book, codex: mystical oracle, opencode: space mission). This is a non-breaking tooling/workflow change. From e3cbaeff7ae1c7baa16b9637fddf82101831c5e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 21:29:39 +0000 Subject: [PATCH 07/14] Update smoke workflows to create discussions and comment on both PR and discussion - Removed `discussion: true` flag from add-comment (not needed, auto-detects) - Added `create-discussion` safe-output with expires: 2h and close-older-discussions - Increased add-comment max from 1 to 2 to allow commenting on both PR and discussion - Updated test requirement #7 to create a new discussion instead of querying existing ones - Updated output instructions to comment on both the PR (brief test results) and the created discussion (fun themed comment) - Removed github-queries-safe-input import (no longer needed) Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 462 +++------------------- .github/workflows/smoke-claude.md | 10 +- .github/workflows/smoke-codex.lock.yml | 462 +++------------------- .github/workflows/smoke-codex.md | 10 +- .github/workflows/smoke-copilot.lock.yml | 462 +++------------------- .github/workflows/smoke-copilot.md | 10 +- .github/workflows/smoke-opencode.lock.yml | 462 +++------------------- .github/workflows/smoke-opencode.md | 10 +- 8 files changed, 260 insertions(+), 1628 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 210bdbc688..3630d40e3f 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -24,7 +24,6 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md -# - shared/github-queries-safe-input.md # - shared/mcp-pagination.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -211,7 +210,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"add_labels":{"allowed":["smoke-claude"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-claude"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -256,7 +255,33 @@ jobs: "name": "create_issue" }, { - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", + "type": "string" + }, + "category": { + "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", + "type": "string" + }, + "title": { + "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_discussion" + }, + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { "additionalProperties": false, "properties": { @@ -399,6 +424,32 @@ jobs: } } }, + "create_discussion": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "category": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -534,92 +585,6 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 - }, - { - "name": "github-discussion-query", - "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of discussions to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-discussion-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-issue-query", - "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of issues to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "Issue state: open, closed, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-issue-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-pr-query", - "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of PRs to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "PR state: open, closed, merged, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-pr-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 } ] } @@ -656,311 +621,6 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh - cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' - #!/bin/bash - # Auto-generated safe-input tool: github-discussion-query - # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # Parse repository owner and name - if [[ -n "$REPO" ]]; then - OWNER=$(echo "$REPO" | cut -d'/' -f1) - NAME=$(echo "$REPO" | cut -d'/' -f2) - else - # Get current repository from GitHub context - OWNER="${GITHUB_REPOSITORY_OWNER:-}" - NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) - fi - - # Validate owner and name - if [[ -z "$OWNER" || -z "$NAME" ]]; then - echo "Error: Could not determine repository owner and name" >&2 - exit 1 - fi - - # Build GraphQL query for discussions - GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' - #!/bin/bash - # Auto-generated safe-input tool: github-issue-query - # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of issue objects", - "item_fields": { - "number": "integer - Issue number", - "title": "string - Issue title", - "state": "string - Issue state (OPEN, CLOSED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "closedAt": "string|null - ISO timestamp of close", - "body": "string - Issue body content", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "comments": "object - Comments info with totalCount field", - "milestone": "object|null - Milestone info with title field", - "url": "string - Issue URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, - {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-issue-query - chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh - cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' - #!/bin/bash - # Auto-generated safe-input tool: github-pr-query - # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of pull request objects", - "item_fields": { - "number": "integer - PR number", - "title": "string - PR title", - "state": "string - PR state (OPEN, CLOSED, MERGED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "mergedAt": "string|null - ISO timestamp of merge", - "closedAt": "string|null - ISO timestamp of close", - "headRefName": "string - Source branch name", - "baseRefName": "string - Target branch name", - "isDraft": "boolean - Whether PR is a draft", - "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", - "additions": "integer - Lines added", - "deletions": "integer - Lines deleted", - "changedFiles": "integer - Number of files changed", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "reviewRequests": "array - Array of review request objects", - "url": "string - PR URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, - {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-pr-query - chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -988,7 +648,6 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -1006,7 +665,6 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -1024,7 +682,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="claude" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -1201,7 +859,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop + **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1439,8 +1097,6 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) - - # Smoke Test: Claude Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1454,7 +1110,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) + 8. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a comic-book style title about the smoke test (be creative and use comic-book language) ## Output @@ -1471,7 +1127,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **fun comic-book style comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and use comic-book language) + 3. Add a **fun comic-book style comment** to the created discussion mentioning that this is a smoke test validation (be playful and use comic-book language) If all tests pass, add the label `smoke-claude` to the pull request. @@ -2137,7 +1793,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 99d4b0eb60..6a70c02079 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -23,7 +23,6 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md - - shared/github-queries-safe-input.md network: allowed: - defaults @@ -51,11 +50,14 @@ runtimes: safe-outputs: add-comment: hide-older-comments: true - discussion: true + max: 2 create-issue: expires: 2h group: true close-older-issues: true + create-discussion: + expires: 2h + close-older-discussions: true add-labels: allowed: [smoke-claude] messages: @@ -79,7 +81,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-${{ github.run_id }}.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) +8. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a comic-book style title about the smoke test (be creative and use comic-book language) ## Output @@ -96,6 +98,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **fun comic-book style comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and use comic-book language) +3. Add a **fun comic-book style comment** to the created discussion mentioning that this is a smoke test validation (be playful and use comic-book language) If all tests pass, add the label `smoke-claude` to the pull request. diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 81c9d92c8d..568c559761 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -24,7 +24,6 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md -# - shared/github-queries-safe-input.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -210,7 +209,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"add_labels":{"allowed":["smoke-codex"],"max":3},"create_issue":{"max":1},"hide_comment":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-codex"],"max":3},"create_discussion":{"max":1},"create_issue":{"max":1},"hide_comment":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -255,7 +254,33 @@ jobs: "name": "create_issue" }, { - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", + "type": "string" + }, + "category": { + "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", + "type": "string" + }, + "title": { + "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_discussion" + }, + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { "additionalProperties": false, "properties": { @@ -426,6 +451,32 @@ jobs: } } }, + "create_discussion": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "category": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -561,92 +612,6 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 - }, - { - "name": "github-discussion-query", - "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of discussions to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-discussion-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-issue-query", - "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of issues to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "Issue state: open, closed, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-issue-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-pr-query", - "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of PRs to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "PR state: open, closed, merged, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-pr-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 } ] } @@ -683,311 +648,6 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh - cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' - #!/bin/bash - # Auto-generated safe-input tool: github-discussion-query - # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # Parse repository owner and name - if [[ -n "$REPO" ]]; then - OWNER=$(echo "$REPO" | cut -d'/' -f1) - NAME=$(echo "$REPO" | cut -d'/' -f2) - else - # Get current repository from GitHub context - OWNER="${GITHUB_REPOSITORY_OWNER:-}" - NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) - fi - - # Validate owner and name - if [[ -z "$OWNER" || -z "$NAME" ]]; then - echo "Error: Could not determine repository owner and name" >&2 - exit 1 - fi - - # Build GraphQL query for discussions - GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' - #!/bin/bash - # Auto-generated safe-input tool: github-issue-query - # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of issue objects", - "item_fields": { - "number": "integer - Issue number", - "title": "string - Issue title", - "state": "string - Issue state (OPEN, CLOSED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "closedAt": "string|null - ISO timestamp of close", - "body": "string - Issue body content", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "comments": "object - Comments info with totalCount field", - "milestone": "object|null - Milestone info with title field", - "url": "string - Issue URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, - {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-issue-query - chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh - cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' - #!/bin/bash - # Auto-generated safe-input tool: github-pr-query - # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of pull request objects", - "item_fields": { - "number": "integer - PR number", - "title": "string - PR title", - "state": "string - PR state (OPEN, CLOSED, MERGED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "mergedAt": "string|null - ISO timestamp of merge", - "closedAt": "string|null - ISO timestamp of close", - "headRefName": "string - Source branch name", - "baseRefName": "string - Target branch name", - "isDraft": "boolean - Whether PR is a draft", - "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", - "additions": "integer - Lines added", - "deletions": "integer - Lines deleted", - "changedFiles": "integer - Number of files changed", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "reviewRequests": "array - Array of review request objects", - "url": "string - PR URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, - {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-pr-query - chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -1015,7 +675,6 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -1033,7 +692,6 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -1051,7 +709,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="codex" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat > /tmp/gh-aw/mcp-config/config.toml << EOF [history] @@ -1294,7 +952,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_issue, hide_comment, missing_tool, noop, remove_labels + **Available tools**: add_comment, add_labels, create_discussion, create_issue, hide_comment, missing_tool, noop, remove_labels **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1421,8 +1079,6 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) - - # Smoke Test: Codex Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1436,7 +1092,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a mystical, oracle-themed comment stating that the smoke test agent was here (be creative and use mystical language) + 8. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a mystical, oracle-themed title about the smoke test (be creative and use mystical language) ## Output @@ -1445,7 +1101,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - Add a **mystical oracle-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use mystical language) + Add a **mystical oracle-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use mystical language) If all tests pass, add the label `smoke-codex` to the pull request. @@ -1992,7 +1648,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index fb60ecace3..7d3c4c6e4a 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -19,7 +19,6 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md - - shared/github-queries-safe-input.md network: allowed: - defaults @@ -46,10 +45,13 @@ sandbox: safe-outputs: add-comment: hide-older-comments: true - discussion: true + max: 2 create-issue: expires: 2h close-older-issues: true + create-discussion: + expires: 2h + close-older-discussions: true add-labels: allowed: [smoke-codex] remove-labels: @@ -76,7 +78,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-${{ github.run_id }}.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a mystical, oracle-themed comment stating that the smoke test agent was here (be creative and use mystical language) +8. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a mystical, oracle-themed title about the smoke test (be creative and use mystical language) ## Output @@ -85,6 +87,6 @@ Add a **very brief** comment (max 5-10 lines) to the current pull request with: - ✅ or ❌ for each test result - Overall status: PASS or FAIL -Add a **mystical oracle-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use mystical language) +Add a **mystical oracle-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use mystical language) If all tests pass, add the label `smoke-codex` to the pull request. diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 0df479db19..0ab792629c 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -24,7 +24,6 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md -# - shared/github-queries-safe-input.md # - shared/reporting.md name: "Smoke Copilot" @@ -228,7 +227,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"add_labels":{"allowed":["smoke-copilot"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-copilot"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -273,7 +272,33 @@ jobs: "name": "create_issue" }, { - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", + "type": "string" + }, + "category": { + "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", + "type": "string" + }, + "title": { + "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_discussion" + }, + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { "additionalProperties": false, "properties": { @@ -416,6 +441,32 @@ jobs: } } }, + "create_discussion": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "category": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -551,92 +602,6 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 - }, - { - "name": "github-discussion-query", - "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of discussions to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-discussion-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-issue-query", - "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of issues to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "Issue state: open, closed, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-issue-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-pr-query", - "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of PRs to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "PR state: open, closed, merged, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-pr-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 } ] } @@ -673,311 +638,6 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh - cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' - #!/bin/bash - # Auto-generated safe-input tool: github-discussion-query - # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # Parse repository owner and name - if [[ -n "$REPO" ]]; then - OWNER=$(echo "$REPO" | cut -d'/' -f1) - NAME=$(echo "$REPO" | cut -d'/' -f2) - else - # Get current repository from GitHub context - OWNER="${GITHUB_REPOSITORY_OWNER:-}" - NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) - fi - - # Validate owner and name - if [[ -z "$OWNER" || -z "$NAME" ]]; then - echo "Error: Could not determine repository owner and name" >&2 - exit 1 - fi - - # Build GraphQL query for discussions - GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' - #!/bin/bash - # Auto-generated safe-input tool: github-issue-query - # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of issue objects", - "item_fields": { - "number": "integer - Issue number", - "title": "string - Issue title", - "state": "string - Issue state (OPEN, CLOSED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "closedAt": "string|null - ISO timestamp of close", - "body": "string - Issue body content", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "comments": "object - Comments info with totalCount field", - "milestone": "object|null - Milestone info with title field", - "url": "string - Issue URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, - {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-issue-query - chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh - cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' - #!/bin/bash - # Auto-generated safe-input tool: github-pr-query - # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of pull request objects", - "item_fields": { - "number": "integer - PR number", - "title": "string - PR title", - "state": "string - PR state (OPEN, CLOSED, MERGED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "mergedAt": "string|null - ISO timestamp of merge", - "closedAt": "string|null - ISO timestamp of close", - "headRefName": "string - Source branch name", - "baseRefName": "string - Target branch name", - "isDraft": "boolean - Whether PR is a draft", - "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", - "additions": "integer - Lines added", - "deletions": "integer - Lines deleted", - "changedFiles": "integer - Number of files changed", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "reviewRequests": "array - Array of review request objects", - "url": "string - PR URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, - {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-pr-query - chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -1005,7 +665,6 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -1023,7 +682,6 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -1041,7 +699,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh @@ -1205,7 +863,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop, remove_labels + **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop, remove_labels **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1330,8 +988,6 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) - - # Smoke Test: Copilot Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1344,7 +1000,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment) + 7. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a fun title about the smoke test (be creative and playful) ## Output @@ -1363,7 +1019,7 @@ jobs: - Overall status: PASS or FAIL - Mention the pull request author and any assignees - 3. Add a **fun and creative comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and entertaining) + 3. Add a **fun and creative comment** to the created discussion mentioning that this is a smoke test validation (be playful and entertaining) If all tests pass: - Add the label `smoke-copilot` to the pull request @@ -1941,7 +1597,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index e05c9ab47f..e3e1b86893 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -18,7 +18,6 @@ engine: copilot imports: - shared/gh.md - shared/reporting.md - - shared/github-queries-safe-input.md network: allowed: - defaults @@ -48,11 +47,14 @@ sandbox: safe-outputs: add-comment: hide-older-comments: true - discussion: true + max: 2 create-issue: expires: 2h group: true close-older-issues: true + create-discussion: + expires: 2h + close-older-discussions: true add-labels: allowed: [smoke-copilot] remove-labels: @@ -79,7 +81,7 @@ strict: true 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-${{ github.run_id }}.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment stating that the smoke test agent was here (be creative and playful with the comment) +7. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a fun title about the smoke test (be creative and playful) ## Output @@ -98,7 +100,7 @@ strict: true - Overall status: PASS or FAIL - Mention the pull request author and any assignees -3. Add a **fun and creative comment** to the randomly selected discussion mentioning that the smoke test was here (be playful and entertaining) +3. Add a **fun and creative comment** to the created discussion mentioning that this is a smoke test validation (be playful and entertaining) If all tests pass: - Add the label `smoke-copilot` to the pull request diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 0025ae1391..6a2b8fe8e7 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -24,7 +24,6 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md -# - shared/github-queries-safe-input.md # - shared/opencode.md name: "Smoke OpenCode" @@ -184,7 +183,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":1},"add_labels":{"allowed":["smoke-opencode"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-opencode"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -229,7 +228,33 @@ jobs: "name": "create_issue" }, { - "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 1 comment(s) can be added.", + "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", + "inputSchema": { + "additionalProperties": false, + "properties": { + "body": { + "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", + "type": "string" + }, + "category": { + "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", + "type": "string" + }, + "title": { + "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", + "type": "string" + } + }, + "required": [ + "title", + "body" + ], + "type": "object" + }, + "name": "create_discussion" + }, + { + "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { "additionalProperties": false, "properties": { @@ -372,6 +397,32 @@ jobs: } } }, + "create_discussion": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "category": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, "create_issue": { "defaultMax": 1, "fields": { @@ -507,92 +558,6 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 - }, - { - "name": "github-discussion-query", - "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of discussions to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-discussion-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-issue-query", - "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of issues to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "Issue state: open, closed, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-issue-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 - }, - { - "name": "github-pr-query", - "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", - "inputSchema": { - "properties": { - "jq": { - "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", - "type": "string" - }, - "limit": { - "description": "Maximum number of PRs to fetch (default: 30)", - "type": "number" - }, - "repo": { - "description": "Repository in owner/repo format (defaults to current repository)", - "type": "string" - }, - "state": { - "description": "PR state: open, closed, merged, all (default: open)", - "type": "string" - } - }, - "type": "object" - }, - "handler": "github-pr-query.sh", - "env": { - "GH_TOKEN": "GH_TOKEN" - }, - "timeout": 60 } ] } @@ -629,311 +594,6 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh - cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' - #!/bin/bash - # Auto-generated safe-input tool: github-discussion-query - # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # Parse repository owner and name - if [[ -n "$REPO" ]]; then - OWNER=$(echo "$REPO" | cut -d'/' -f1) - NAME=$(echo "$REPO" | cut -d'/' -f2) - else - # Get current repository from GitHub context - OWNER="${GITHUB_REPOSITORY_OWNER:-}" - NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) - fi - - # Validate owner and name - if [[ -z "$OWNER" || -z "$NAME" ]]; then - echo "Error: Could not determine repository owner and name" >&2 - exit 1 - fi - - # Build GraphQL query for discussions - GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' - #!/bin/bash - # Auto-generated safe-input tool: github-issue-query - # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of issue objects", - "item_fields": { - "number": "integer - Issue number", - "title": "string - Issue title", - "state": "string - Issue state (OPEN, CLOSED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "closedAt": "string|null - ISO timestamp of close", - "body": "string - Issue body content", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "comments": "object - Comments info with totalCount field", - "milestone": "object|null - Milestone info with title field", - "url": "string - Issue URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, - {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-issue-query - chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh - cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' - #!/bin/bash - # Auto-generated safe-input tool: github-pr-query - # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. - - set -euo pipefail - - set -e - - # Default values - REPO="${INPUT_REPO:-}" - STATE="${INPUT_STATE:-open}" - LIMIT="${INPUT_LIMIT:-30}" - JQ_FILTER="${INPUT_JQ:-}" - - # JSON fields to fetch - JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" - - # Build and execute gh command - if [[ -n "$REPO" ]]; then - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") - else - OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") - fi - - # Apply jq filter if specified - if [[ -n "$JQ_FILTER" ]]; then - jq "$JQ_FILTER" <<< "$OUTPUT" - else - # Return schema and size instead of full data - ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") - DATA_SIZE=${#OUTPUT} - - # Validate values are numeric - if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then - ITEM_COUNT=0 - fi - if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then - DATA_SIZE=0 - fi - - cat << EOF - { - "message": "No --jq filter provided. Use --jq to filter and retrieve data.", - "item_count": $ITEM_COUNT, - "data_size_bytes": $DATA_SIZE, - "schema": { - "type": "array", - "description": "Array of pull request objects", - "item_fields": { - "number": "integer - PR number", - "title": "string - PR title", - "state": "string - PR state (OPEN, CLOSED, MERGED)", - "author": "object - Author info with login field", - "createdAt": "string - ISO timestamp of creation", - "updatedAt": "string - ISO timestamp of last update", - "mergedAt": "string|null - ISO timestamp of merge", - "closedAt": "string|null - ISO timestamp of close", - "headRefName": "string - Source branch name", - "baseRefName": "string - Target branch name", - "isDraft": "boolean - Whether PR is a draft", - "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", - "additions": "integer - Lines added", - "deletions": "integer - Lines deleted", - "changedFiles": "integer - Number of files changed", - "labels": "array - Array of label objects with name field", - "assignees": "array - Array of assignee objects with login field", - "reviewRequests": "array - Array of review request objects", - "url": "string - PR URL" - } - }, - "suggested_queries": [ - {"description": "Get all data", "query": "."}, - {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, - {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, - {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, - {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, - {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, - {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} - ] - } - EOF - fi - - - EOFSH_github-pr-query - chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -961,7 +621,6 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -979,7 +638,6 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | @@ -996,7 +654,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="custom" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -1112,7 +770,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop + **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1169,8 +827,6 @@ jobs: - - # Smoke Test: OpenCode Custom Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1183,7 +839,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment stating that the smoke test agent was here (be creative and use space mission language) + 7. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a space/rocket-themed title about the smoke test (be creative and use space mission language) ## Output @@ -1200,7 +856,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **space/rocket-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use space mission language) + 3. Add a **space/rocket-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use space mission language) If all tests pass, add the label `smoke-opencode` to the pull request. @@ -1843,7 +1499,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"hide_older_comments\":true,\"max\":1},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-opencode.md b/.github/workflows/smoke-opencode.md index 3cfa9f73d2..de6d8843bd 100644 --- a/.github/workflows/smoke-opencode.md +++ b/.github/workflows/smoke-opencode.md @@ -17,7 +17,6 @@ name: Smoke OpenCode imports: - shared/opencode.md - shared/gh.md - - shared/github-queries-safe-input.md strict: true sandbox: mcp: @@ -38,11 +37,14 @@ tools: safe-outputs: add-comment: hide-older-comments: true - discussion: true + max: 2 create-issue: expires: 2h group: true close-older-issues: true + create-discussion: + expires: 2h + close-older-discussions: true add-labels: allowed: [smoke-opencode] messages: @@ -65,7 +67,7 @@ timeout-minutes: 10 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-${{ github.run_id }}.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get 1 random discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment stating that the smoke test agent was here (be creative and use space mission language) +7. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a space/rocket-themed title about the smoke test (be creative and use space mission language) ## Output @@ -82,6 +84,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **space/rocket-themed comment** to the randomly selected discussion mentioning that the smoke test was here (be creative and use space mission language) +3. Add a **space/rocket-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use space mission language) If all tests pass, add the label `smoke-opencode` to the pull request. From b055e76ac16a47354552b1dc134dfd9966cd29f7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:50:04 +0000 Subject: [PATCH 08/14] Add codemod to remove deprecated discussion field and mark it as deprecated in schema - Created codemod_discussion_flag.go to automatically remove `safe-outputs.add-comment.discussion: true` - Added comprehensive test suite for the new codemod in codemod_discussion_flag_test.go - Registered the new codemod in GetAllCodemods() as "add-comment-discussion-removal" - Updated JSON schema to mark the discussion field as deprecated with migration instructions - Updated fix_codemods_test.go to reflect 14 codemods (was 13) - Tested with real workflow file - successfully removes discussion flag while preserving other fields Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/cli/codemod_discussion_flag.go | 136 +++++++++ pkg/cli/codemod_discussion_flag_test.go | 294 +++++++++++++++++++ pkg/cli/fix_codemods.go | 1 + pkg/cli/fix_codemods_test.go | 3 +- pkg/parser/schemas/main_workflow_schema.json | 3 +- 5 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 pkg/cli/codemod_discussion_flag.go create mode 100644 pkg/cli/codemod_discussion_flag_test.go diff --git a/pkg/cli/codemod_discussion_flag.go b/pkg/cli/codemod_discussion_flag.go new file mode 100644 index 0000000000..bcec260650 --- /dev/null +++ b/pkg/cli/codemod_discussion_flag.go @@ -0,0 +1,136 @@ +package cli + +import ( + "strings" + + "github.com/githubnext/gh-aw/pkg/logger" +) + +var discussionFlagCodemodLog = logger.New("cli:codemod_discussion_flag") + +// getDiscussionFlagRemovalCodemod creates a codemod for removing the deprecated discussion field from add-comment +func getDiscussionFlagRemovalCodemod() Codemod { + return Codemod{ + ID: "add-comment-discussion-removal", + Name: "Remove deprecated add-comment.discussion field", + Description: "Removes the deprecated 'safe-outputs.add-comment.discussion' field (detection is now automatic based on context)", + IntroducedIn: "0.3.0", + Apply: func(content string, frontmatter map[string]any) (string, bool, error) { + // Check if safe-outputs exists + safeOutputsValue, hasSafeOutputs := frontmatter["safe-outputs"] + if !hasSafeOutputs { + return content, false, nil + } + + safeOutputsMap, ok := safeOutputsValue.(map[string]any) + if !ok { + return content, false, nil + } + + // Check if add-comment exists in safe-outputs + addCommentValue, hasAddComment := safeOutputsMap["add-comment"] + if !hasAddComment { + return content, false, nil + } + + addCommentMap, ok := addCommentValue.(map[string]any) + if !ok { + return content, false, nil + } + + // Check if discussion field exists in add-comment + _, hasDiscussion := addCommentMap["discussion"] + if !hasDiscussion { + return content, false, nil + } + + // Parse frontmatter to get raw lines + frontmatterLines, markdown, err := parseFrontmatterLines(content) + if err != nil { + return content, false, err + } + + // Remove the discussion field from the add-comment block in safe-outputs + var result []string + var modified bool + var inSafeOutputsBlock bool + var safeOutputsIndent string + var inAddCommentBlock bool + var addCommentIndent string + var inDiscussionField bool + + for i, line := range frontmatterLines { + trimmedLine := strings.TrimSpace(line) + + // Track if we're in the safe-outputs block + if strings.HasPrefix(trimmedLine, "safe-outputs:") { + inSafeOutputsBlock = true + safeOutputsIndent = getIndentation(line) + result = append(result, line) + continue + } + + // Check if we've left the safe-outputs block + if inSafeOutputsBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if hasExitedBlock(line, safeOutputsIndent) { + inSafeOutputsBlock = false + inAddCommentBlock = false + } + } + + // Track if we're in the add-comment block within safe-outputs + if inSafeOutputsBlock && strings.HasPrefix(trimmedLine, "add-comment:") { + inAddCommentBlock = true + addCommentIndent = getIndentation(line) + result = append(result, line) + continue + } + + // Check if we've left the add-comment block + if inAddCommentBlock && len(trimmedLine) > 0 && !strings.HasPrefix(trimmedLine, "#") { + if hasExitedBlock(line, addCommentIndent) { + inAddCommentBlock = false + } + } + + // Remove discussion field line if in add-comment block + if inAddCommentBlock && strings.HasPrefix(trimmedLine, "discussion:") { + modified = true + inDiscussionField = true + discussionFlagCodemodLog.Printf("Removed safe-outputs.add-comment.discussion on line %d", i+1) + continue + } + + // Skip any nested content under the discussion field (shouldn't be any, but for completeness) + if inDiscussionField { + // Empty lines within the field block should be removed + if len(trimmedLine) == 0 { + continue + } + + currentIndent := getIndentation(line) + discussionIndent := addCommentIndent + " " // discussion would be 2 spaces more than add-comment + + // If this line has more indentation than discussion field, skip it + if len(currentIndent) > len(discussionIndent) { + discussionFlagCodemodLog.Printf("Removed nested discussion property on line %d: %s", i+1, trimmedLine) + continue + } + // We've exited the discussion field + inDiscussionField = false + } + + result = append(result, line) + } + + if !modified { + return content, false, nil + } + + // Reconstruct the content + newContent := reconstructContent(result, markdown) + discussionFlagCodemodLog.Print("Applied add-comment.discussion removal") + return newContent, true, nil + }, + } +} diff --git a/pkg/cli/codemod_discussion_flag_test.go b/pkg/cli/codemod_discussion_flag_test.go new file mode 100644 index 0000000000..cc481e8e09 --- /dev/null +++ b/pkg/cli/codemod_discussion_flag_test.go @@ -0,0 +1,294 @@ +package cli + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGetDiscussionFlagRemovalCodemod(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + assert.Equal(t, "add-comment-discussion-removal", codemod.ID) + assert.Equal(t, "Remove deprecated add-comment.discussion field", codemod.Name) + assert.NotEmpty(t, codemod.Description) + assert.Equal(t, "0.3.0", codemod.IntroducedIn) + require.NotNil(t, codemod.Apply) +} + +func TestDiscussionFlagCodemod_RemovesDiscussionFlag(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + hide-older-comments: true + discussion: true + max: 2 + create-issue: + expires: 2h +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "hide-older-comments": true, + "discussion": true, + "max": 2, + }, + "create-issue": map[string]any{ + "expires": "2h", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "discussion:") + assert.Contains(t, result, "hide-older-comments: true") + assert.Contains(t, result, "max: 2") + assert.Contains(t, result, "expires: 2h") +} + +func TestDiscussionFlagCodemod_NoSafeOutputsField(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +permissions: + contents: read +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "permissions": map[string]any{ + "contents": "read", + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.False(t, applied) + assert.Equal(t, content, result) +} + +func TestDiscussionFlagCodemod_NoAddCommentField(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + create-issue: + expires: 2h +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "create-issue": map[string]any{ + "expires": "2h", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.False(t, applied) + assert.Equal(t, content, result) +} + +func TestDiscussionFlagCodemod_NoDiscussionField(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + hide-older-comments: true + max: 2 +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "hide-older-comments": true, + "max": 2, + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.False(t, applied) + assert.Equal(t, content, result) +} + +func TestDiscussionFlagCodemod_PreservesIndentation(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + hide-older-comments: true + discussion: true + max: 2 + create-issue: + expires: 2h +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "hide-older-comments": true, + "discussion": true, + "max": 2, + }, + "create-issue": map[string]any{ + "expires": "2h", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "discussion:") + assert.Contains(t, result, " add-comment:") + assert.Contains(t, result, " hide-older-comments: true") + assert.Contains(t, result, " max: 2") +} + +func TestDiscussionFlagCodemod_PreservesComments(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + hide-older-comments: true # Hide older comments + discussion: true # Target discussions + max: 2 # Maximum comments +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "hide-older-comments": true, + "discussion": true, + "max": 2, + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.NotContains(t, result, "discussion:") + assert.Contains(t, result, "hide-older-comments: true # Hide older comments") + assert.Contains(t, result, "max: 2 # Maximum comments") +} + +func TestDiscussionFlagCodemod_PreservesMarkdown(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + discussion: true +--- + +# Test Workflow + +This workflow uses add-comment with discussion support.` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "discussion": true, + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + assert.Contains(t, result, "# Test Workflow") + assert.Contains(t, result, "This workflow uses add-comment with discussion support.") +} + +func TestDiscussionFlagCodemod_MultipleFields(t *testing.T) { + codemod := getDiscussionFlagRemovalCodemod() + + content := `--- +on: workflow_dispatch +safe-outputs: + add-comment: + max: 1 + target: "*" + discussion: true + hide-older-comments: false + target-repo: "owner/repo" + create-discussion: + expires: 24h +--- + +# Test` + + frontmatter := map[string]any{ + "on": "workflow_dispatch", + "safe-outputs": map[string]any{ + "add-comment": map[string]any{ + "max": 1, + "target": "*", + "discussion": true, + "hide-older-comments": false, + "target-repo": "owner/repo", + }, + "create-discussion": map[string]any{ + "expires": "24h", + }, + }, + } + + result, applied, err := codemod.Apply(content, frontmatter) + + require.NoError(t, err) + assert.True(t, applied) + // Check that "discussion: true" is not present (but "create-discussion:" is still there) + assert.NotContains(t, result, " discussion: true") + assert.Contains(t, result, "max: 1") + assert.Contains(t, result, "target: \"*\"") + assert.Contains(t, result, "hide-older-comments: false") + assert.Contains(t, result, "target-repo: \"owner/repo\"") + assert.Contains(t, result, "create-discussion:") + assert.Contains(t, result, "expires: 24h") +} diff --git a/pkg/cli/fix_codemods.go b/pkg/cli/fix_codemods.go index 2872fe80ad..71218a20cd 100644 --- a/pkg/cli/fix_codemods.go +++ b/pkg/cli/fix_codemods.go @@ -31,5 +31,6 @@ func GetAllCodemods() []Codemod { getDeleteSchemaFileCodemod(), getGrepToolRemovalCodemod(), getMCPNetworkMigrationCodemod(), + getDiscussionFlagRemovalCodemod(), } } diff --git a/pkg/cli/fix_codemods_test.go b/pkg/cli/fix_codemods_test.go index 8ce5e0642e..2c8301e0ff 100644 --- a/pkg/cli/fix_codemods_test.go +++ b/pkg/cli/fix_codemods_test.go @@ -41,7 +41,7 @@ func TestGetAllCodemods_ReturnsAllCodemods(t *testing.T) { codemods := GetAllCodemods() // Verify we have the expected number of codemods - expectedCount := 13 + expectedCount := 14 assert.Len(t, codemods, expectedCount, "Should return all %d codemods", expectedCount) // Verify all codemods have required fields @@ -115,6 +115,7 @@ func TestGetAllCodemods_InExpectedOrder(t *testing.T) { "delete-schema-file", "grep-tool-removal", "mcp-network-to-top-level-migration", + "add-comment-discussion-removal", } require.Len(t, codemods, len(expectedOrder), "Should have expected number of codemods") diff --git a/pkg/parser/schemas/main_workflow_schema.json b/pkg/parser/schemas/main_workflow_schema.json index cac7448c38..3ed640e89c 100644 --- a/pkg/parser/schemas/main_workflow_schema.json +++ b/pkg/parser/schemas/main_workflow_schema.json @@ -4491,7 +4491,8 @@ "discussion": { "type": "boolean", "const": true, - "description": "Target discussion comments instead of issue/PR comments. Must be true if present." + "description": "DEPRECATED: This field is deprecated and will be removed in a future version. The add_comment handler now automatically detects whether to target discussions based on context (discussion/discussion_comment events) or the item_number field provided by the agent. Remove this field from your workflow configuration.", + "deprecated": true }, "hide-older-comments": { "type": "boolean", From b9b5335153eab120c2528e3c8b415ad3a730f3b3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 22 Jan 2026 22:57:52 +0000 Subject: [PATCH 09/14] Add changeset [skip-ci] --- .changeset/patch-add-discussion-smoke-workflows.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/patch-add-discussion-smoke-workflows.md diff --git a/.changeset/patch-add-discussion-smoke-workflows.md b/.changeset/patch-add-discussion-smoke-workflows.md new file mode 100644 index 0000000000..8eda7cabe8 --- /dev/null +++ b/.changeset/patch-add-discussion-smoke-workflows.md @@ -0,0 +1,8 @@ +--- +"gh-aw": patch +--- + +Add discussion creation and commenting to smoke workflows; deprecate the `discussion` flag and add a codemod to remove it. + +This change updates smoke workflows to create discussions and post themed comments, adds safe-output tooling for creating discussions, and includes a compiler change and codemod to migrate away from the deprecated `discussion` field. + From 1bb853e3cdb765c3cd2391b75acb11f34ca85386 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 23:15:33 +0000 Subject: [PATCH 10/14] Remove create-discussion, use github-discussion-query to pick latest discussion instead - Removed create-discussion safe-output from all 4 smoke workflows - Re-added github-queries-safe-input.md import to all smoke workflows - Updated test requirement #7/#8 to query the latest discussion using github-discussion-query with limit=1, jq=".[0]" - Updated output instructions to comment on the latest discussion instead of a created one - Agents now pick the latest available discussion and add themed comments to it - Recompiled all workflows successfully Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 460 +++++++++++++++++++--- .github/workflows/smoke-claude.md | 8 +- .github/workflows/smoke-codex.lock.yml | 460 +++++++++++++++++++--- .github/workflows/smoke-codex.md | 8 +- .github/workflows/smoke-copilot.lock.yml | 460 +++++++++++++++++++--- .github/workflows/smoke-copilot.md | 8 +- .github/workflows/smoke-opencode.lock.yml | 460 +++++++++++++++++++--- .github/workflows/smoke-opencode.md | 8 +- 8 files changed, 1620 insertions(+), 252 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 3630d40e3f..2020cf0c40 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/mcp-pagination.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -210,7 +211,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-claude"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-claude"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -254,32 +255,6 @@ jobs: }, "name": "create_issue" }, - { - "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", - "type": "string" - }, - "category": { - "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", - "type": "string" - }, - "title": { - "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_discussion" - }, { "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { @@ -424,32 +399,6 @@ jobs: } } }, - "create_discussion": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "category": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, "create_issue": { "defaultMax": 1, "fields": { @@ -585,6 +534,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -621,6 +656,311 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # Parse repository owner and name + if [[ -n "$REPO" ]]; then + OWNER=$(echo "$REPO" | cut -d'/' -f1) + NAME=$(echo "$REPO" | cut -d'/' -f2) + else + # Get current repository from GitHub context + OWNER="${GITHUB_REPOSITORY_OWNER:-}" + NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) + fi + + # Validate owner and name + if [[ -z "$OWNER" || -z "$NAME" ]]; then + echo "Error: Could not determine repository owner and name" >&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -648,6 +988,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -665,6 +1006,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -682,7 +1024,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="claude" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -859,7 +1201,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop + **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1097,6 +1439,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Claude Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1110,7 +1454,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a comic-book style title about the smoke test (be creative and use comic-book language) + 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) ## Output @@ -1127,7 +1471,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **fun comic-book style comment** to the created discussion mentioning that this is a smoke test validation (be playful and use comic-book language) + 3. Add a **fun comic-book style comment** to the latest discussion mentioning that the smoke test was here (be playful and use comic-book language) If all tests pass, add the label `smoke-claude` to the pull request. @@ -1793,7 +2137,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-claude\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 6a70c02079..3989789e74 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -23,6 +23,7 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -55,9 +56,6 @@ safe-outputs: expires: 2h group: true close-older-issues: true - create-discussion: - expires: 2h - close-older-discussions: true add-labels: allowed: [smoke-claude] messages: @@ -81,7 +79,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-${{ github.run_id }}.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a comic-book style title about the smoke test (be creative and use comic-book language) +8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) ## Output @@ -98,6 +96,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **fun comic-book style comment** to the created discussion mentioning that this is a smoke test validation (be playful and use comic-book language) +3. Add a **fun comic-book style comment** to the latest discussion mentioning that the smoke test was here (be playful and use comic-book language) If all tests pass, add the label `smoke-claude` to the pull request. diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 568c559761..1adc1d3384 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/mcp/tavily.md # - shared/reporting.md @@ -209,7 +210,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-codex"],"max":3},"create_discussion":{"max":1},"create_issue":{"max":1},"hide_comment":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-codex"],"max":3},"create_issue":{"max":1},"hide_comment":{"max":5},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -253,32 +254,6 @@ jobs: }, "name": "create_issue" }, - { - "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", - "type": "string" - }, - "category": { - "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", - "type": "string" - }, - "title": { - "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_discussion" - }, { "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { @@ -451,32 +426,6 @@ jobs: } } }, - "create_discussion": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "category": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, "create_issue": { "defaultMax": 1, "fields": { @@ -612,6 +561,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -648,6 +683,311 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # Parse repository owner and name + if [[ -n "$REPO" ]]; then + OWNER=$(echo "$REPO" | cut -d'/' -f1) + NAME=$(echo "$REPO" | cut -d'/' -f2) + else + # Get current repository from GitHub context + OWNER="${GITHUB_REPOSITORY_OWNER:-}" + NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) + fi + + # Validate owner and name + if [[ -z "$OWNER" || -z "$NAME" ]]; then + echo "Error: Could not determine repository owner and name" >&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -675,6 +1015,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -692,6 +1033,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} TAVILY_API_KEY: ${{ secrets.TAVILY_API_KEY }} @@ -709,7 +1051,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="codex" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -e TAVILY_API_KEY -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat > /tmp/gh-aw/mcp-config/config.toml << EOF [history] @@ -952,7 +1294,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_discussion, create_issue, hide_comment, missing_tool, noop, remove_labels + **Available tools**: add_comment, add_labels, create_issue, hide_comment, missing_tool, noop, remove_labels **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -1079,6 +1421,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Codex Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1092,7 +1436,7 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a mystical, oracle-themed title about the smoke test (be creative and use mystical language) + 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a mystical oracle-themed comment to it stating that the smoke test agent was here (be creative and use mystical language) ## Output @@ -1101,7 +1445,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - Add a **mystical oracle-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use mystical language) + Add a **mystical oracle-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use mystical language) If all tests pass, add the label `smoke-codex` to the pull request. @@ -1648,7 +1992,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-codex\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"max\":1},\"hide_comment\":{\"max\":5},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index 7d3c4c6e4a..aaa674024a 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -19,6 +19,7 @@ imports: - shared/gh.md - shared/mcp/tavily.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -49,9 +50,6 @@ safe-outputs: create-issue: expires: 2h close-older-issues: true - create-discussion: - expires: 2h - close-older-discussions: true add-labels: allowed: [smoke-codex] remove-labels: @@ -78,7 +76,7 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-${{ github.run_id }}.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a mystical, oracle-themed title about the smoke test (be creative and use mystical language) +8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a mystical oracle-themed comment to it stating that the smoke test agent was here (be creative and use mystical language) ## Output @@ -87,6 +85,6 @@ Add a **very brief** comment (max 5-10 lines) to the current pull request with: - ✅ or ❌ for each test result - Overall status: PASS or FAIL -Add a **mystical oracle-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use mystical language) +Add a **mystical oracle-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use mystical language) If all tests pass, add the label `smoke-codex` to the pull request. diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 0ab792629c..47d2b8e49d 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/reporting.md name: "Smoke Copilot" @@ -227,7 +228,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-copilot"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-copilot"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1},"remove_labels":{"allowed":["smoke"],"max":3}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -271,32 +272,6 @@ jobs: }, "name": "create_issue" }, - { - "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", - "type": "string" - }, - "category": { - "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", - "type": "string" - }, - "title": { - "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_discussion" - }, { "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { @@ -441,32 +416,6 @@ jobs: } } }, - "create_discussion": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "category": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, "create_issue": { "defaultMax": 1, "fields": { @@ -602,6 +551,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -638,6 +673,311 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # Parse repository owner and name + if [[ -n "$REPO" ]]; then + OWNER=$(echo "$REPO" | cut -d'/' -f1) + NAME=$(echo "$REPO" | cut -d'/' -f2) + else + # Get current repository from GitHub context + OWNER="${GITHUB_REPOSITORY_OWNER:-}" + NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) + fi + + # Validate owner and name + if [[ -z "$OWNER" || -z "$NAME" ]]; then + echo "Error: Could not determine repository owner and name" >&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -665,6 +1005,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -682,6 +1023,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -699,7 +1041,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' mkdir -p /home/runner/.copilot cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh @@ -863,7 +1205,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop, remove_labels + **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop, remove_labels **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -988,6 +1330,8 @@ jobs: - Include up to 3 most relevant run URLs at end under `**References:**` - Do NOT add footer attribution (system adds automatically) + + # Smoke Test: Copilot Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -1000,7 +1344,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a fun title about the smoke test (be creative and playful) + 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment) ## Output @@ -1019,7 +1363,7 @@ jobs: - Overall status: PASS or FAIL - Mention the pull request author and any assignees - 3. Add a **fun and creative comment** to the created discussion mentioning that this is a smoke test validation (be playful and entertaining) + 3. Add a **fun and creative comment** to the latest discussion mentioning that the smoke test was here (be playful and entertaining) If all tests pass: - Add the label `smoke-copilot` to the pull request @@ -1597,7 +1941,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-copilot\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{},\"remove_labels\":{\"allowed\":[\"smoke\"]}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index e3e1b86893..5a1f4aad86 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -18,6 +18,7 @@ engine: copilot imports: - shared/gh.md - shared/reporting.md + - shared/github-queries-safe-input.md network: allowed: - defaults @@ -52,9 +53,6 @@ safe-outputs: expires: 2h group: true close-older-issues: true - create-discussion: - expires: 2h - close-older-discussions: true add-labels: allowed: [smoke-copilot] remove-labels: @@ -81,7 +79,7 @@ strict: true 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-${{ github.run_id }}.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a fun title about the smoke test (be creative and playful) +7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment) ## Output @@ -100,7 +98,7 @@ strict: true - Overall status: PASS or FAIL - Mention the pull request author and any assignees -3. Add a **fun and creative comment** to the created discussion mentioning that this is a smoke test validation (be playful and entertaining) +3. Add a **fun and creative comment** to the latest discussion mentioning that the smoke test was here (be playful and entertaining) If all tests pass: - Add the label `smoke-copilot` to the pull request diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index 6a2b8fe8e7..e19bb75760 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -24,6 +24,7 @@ # Resolved workflow manifest: # Imports: # - shared/gh.md +# - shared/github-queries-safe-input.md # - shared/opencode.md name: "Smoke OpenCode" @@ -183,7 +184,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'EOF' - {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-opencode"],"max":3},"create_discussion":{"max":1},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} + {"add_comment":{"max":2},"add_labels":{"allowed":["smoke-opencode"],"max":3},"create_issue":{"group":true,"max":1},"missing_data":{},"missing_tool":{},"noop":{"max":1}} EOF cat > /opt/gh-aw/safeoutputs/tools.json << 'EOF' [ @@ -227,32 +228,6 @@ jobs: }, "name": "create_issue" }, - { - "description": "Create a GitHub discussion for announcements, Q\u0026A, reports, status updates, or community conversations. Use this for content that benefits from threaded replies, doesn't require task tracking, or serves as documentation. For actionable work items that need assignment and status tracking, use create_issue instead. CONSTRAINTS: Maximum 1 discussion(s) can be created.", - "inputSchema": { - "additionalProperties": false, - "properties": { - "body": { - "description": "Discussion content in Markdown. Do NOT repeat the title as a heading since it already appears as the discussion's h1. Include all relevant context, findings, or questions.", - "type": "string" - }, - "category": { - "description": "Discussion category by name (e.g., 'General'), slug (e.g., 'general'), or ID. If omitted, uses the first available category. Category must exist in the repository.", - "type": "string" - }, - "title": { - "description": "Concise discussion title summarizing the topic. The title appears as the main heading, so keep it brief and descriptive.", - "type": "string" - } - }, - "required": [ - "title", - "body" - ], - "type": "object" - }, - "name": "create_discussion" - }, { "description": "Add a comment to an existing GitHub issue, pull request, or discussion. Use this to provide feedback, answer questions, or add information to an existing conversation. For creating new items, use create_issue, create_discussion, or create_pull_request instead. CONSTRAINTS: Maximum 2 comment(s) can be added.", "inputSchema": { @@ -397,32 +372,6 @@ jobs: } } }, - "create_discussion": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "category": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "title": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, "create_issue": { "defaultMax": 1, "fields": { @@ -558,6 +507,92 @@ jobs: "GH_DEBUG": "GH_DEBUG" }, "timeout": 60 + }, + { + "name": "github-discussion-query", + "description": "Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of discussions to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-discussion-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-issue-query", + "description": "Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of issues to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "Issue state: open, closed, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-issue-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 + }, + { + "name": "github-pr-query", + "description": "Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter.", + "inputSchema": { + "properties": { + "jq": { + "description": "jq filter expression to apply to output. If not provided, returns schema info instead of full data.", + "type": "string" + }, + "limit": { + "description": "Maximum number of PRs to fetch (default: 30)", + "type": "number" + }, + "repo": { + "description": "Repository in owner/repo format (defaults to current repository)", + "type": "string" + }, + "state": { + "description": "PR state: open, closed, merged, all (default: open)", + "type": "string" + } + }, + "type": "object" + }, + "handler": "github-pr-query.sh", + "env": { + "GH_TOKEN": "GH_TOKEN" + }, + "timeout": 60 } ] } @@ -594,6 +629,311 @@ jobs: EOFSH_gh chmod +x /opt/gh-aw/safe-inputs/gh.sh + cat > /opt/gh-aw/safe-inputs/github-discussion-query.sh << 'EOFSH_github-discussion-query' + #!/bin/bash + # Auto-generated safe-input tool: github-discussion-query + # Query GitHub discussions with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # Parse repository owner and name + if [[ -n "$REPO" ]]; then + OWNER=$(echo "$REPO" | cut -d'/' -f1) + NAME=$(echo "$REPO" | cut -d'/' -f2) + else + # Get current repository from GitHub context + OWNER="${GITHUB_REPOSITORY_OWNER:-}" + NAME=$(echo "${GITHUB_REPOSITORY:-}" | cut -d'/' -f2) + fi + + # Validate owner and name + if [[ -z "$OWNER" || -z "$NAME" ]]; then + echo "Error: Could not determine repository owner and name" >&2 + exit 1 + fi + + # Build GraphQL query for discussions + GRAPHQL_QUERY=$(cat < /opt/gh-aw/safe-inputs/github-issue-query.sh << 'EOFSH_github-issue-query' + #!/bin/bash + # Auto-generated safe-input tool: github-issue-query + # Query GitHub issues with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,closedAt,body,labels,assignees,comments,milestone,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh issue list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of issue objects", + "item_fields": { + "number": "integer - Issue number", + "title": "string - Issue title", + "state": "string - Issue state (OPEN, CLOSED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "closedAt": "string|null - ISO timestamp of close", + "body": "string - Issue body content", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "comments": "object - Comments info with totalCount field", + "milestone": "object|null - Milestone info with title field", + "url": "string - Issue URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get issue numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open issues only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get issues by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get issues with label", "query": ".[] | select(.labels | map(.name) | index(\"bug\"))"}, + {"description": "Get issues with many comments", "query": ".[] | select(.comments.totalCount > 5) | {number, title, comments: .comments.totalCount}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-issue-query + chmod +x /opt/gh-aw/safe-inputs/github-issue-query.sh + cat > /opt/gh-aw/safe-inputs/github-pr-query.sh << 'EOFSH_github-pr-query' + #!/bin/bash + # Auto-generated safe-input tool: github-pr-query + # Query GitHub pull requests with jq filtering support. Without --jq, returns schema and data size info. Use --jq '.' to get all data, or specific jq expressions to filter. + + set -euo pipefail + + set -e + + # Default values + REPO="${INPUT_REPO:-}" + STATE="${INPUT_STATE:-open}" + LIMIT="${INPUT_LIMIT:-30}" + JQ_FILTER="${INPUT_JQ:-}" + + # JSON fields to fetch + JSON_FIELDS="number,title,state,author,createdAt,updatedAt,mergedAt,closedAt,headRefName,baseRefName,isDraft,reviewDecision,additions,deletions,changedFiles,labels,assignees,reviewRequests,url" + + # Build and execute gh command + if [[ -n "$REPO" ]]; then + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS" --repo "$REPO") + else + OUTPUT=$(gh pr list --state "$STATE" --limit "$LIMIT" --json "$JSON_FIELDS") + fi + + # Apply jq filter if specified + if [[ -n "$JQ_FILTER" ]]; then + jq "$JQ_FILTER" <<< "$OUTPUT" + else + # Return schema and size instead of full data + ITEM_COUNT=$(jq 'length' <<< "$OUTPUT") + DATA_SIZE=${#OUTPUT} + + # Validate values are numeric + if ! [[ "$ITEM_COUNT" =~ ^[0-9]+$ ]]; then + ITEM_COUNT=0 + fi + if ! [[ "$DATA_SIZE" =~ ^[0-9]+$ ]]; then + DATA_SIZE=0 + fi + + cat << EOF + { + "message": "No --jq filter provided. Use --jq to filter and retrieve data.", + "item_count": $ITEM_COUNT, + "data_size_bytes": $DATA_SIZE, + "schema": { + "type": "array", + "description": "Array of pull request objects", + "item_fields": { + "number": "integer - PR number", + "title": "string - PR title", + "state": "string - PR state (OPEN, CLOSED, MERGED)", + "author": "object - Author info with login field", + "createdAt": "string - ISO timestamp of creation", + "updatedAt": "string - ISO timestamp of last update", + "mergedAt": "string|null - ISO timestamp of merge", + "closedAt": "string|null - ISO timestamp of close", + "headRefName": "string - Source branch name", + "baseRefName": "string - Target branch name", + "isDraft": "boolean - Whether PR is a draft", + "reviewDecision": "string|null - Review decision (APPROVED, CHANGES_REQUESTED, REVIEW_REQUIRED)", + "additions": "integer - Lines added", + "deletions": "integer - Lines deleted", + "changedFiles": "integer - Number of files changed", + "labels": "array - Array of label objects with name field", + "assignees": "array - Array of assignee objects with login field", + "reviewRequests": "array - Array of review request objects", + "url": "string - PR URL" + } + }, + "suggested_queries": [ + {"description": "Get all data", "query": "."}, + {"description": "Get PR numbers and titles", "query": ".[] | {number, title}"}, + {"description": "Get open PRs only", "query": ".[] | select(.state == \"OPEN\")"}, + {"description": "Get merged PRs", "query": ".[] | select(.mergedAt != null)"}, + {"description": "Get PRs by author", "query": ".[] | select(.author.login == \"USERNAME\")"}, + {"description": "Get large PRs", "query": ".[] | select(.changedFiles > 10) | {number, title, changedFiles}"}, + {"description": "Count by state", "query": "group_by(.state) | map({state: .[0].state, count: length})"} + ] + } + EOF + fi + + + EOFSH_github-pr-query + chmod +x /opt/gh-aw/safe-inputs/github-pr-query.sh - name: Generate Safe Inputs MCP Server Config id: safe-inputs-config @@ -621,6 +961,7 @@ jobs: GH_AW_SAFE_INPUTS_API_KEY: ${{ steps.safe-inputs-config.outputs.safe_inputs_api_key }} GH_AW_GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Environment variables are set above to prevent template injection export GH_AW_SAFE_INPUTS_PORT @@ -638,6 +979,7 @@ jobs: GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} GH_DEBUG: 1 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_MCP_LOCKDOWN: ${{ steps.determine-automatic-lockdown.outputs.lockdown == 'true' && '1' || '0' }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | @@ -654,7 +996,7 @@ jobs: # Register API key as secret to mask it from logs echo "::add-mask::${MCP_GATEWAY_API_KEY}" export GH_AW_ENGINE="custom" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e DEBUG="*" -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_LOCKDOWN -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_INPUTS_PORT -e GH_AW_SAFE_INPUTS_API_KEY -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -e GH_AW_GH_TOKEN -e GH_DEBUG -e GH_TOKEN -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/githubnext/gh-aw-mcpg:v0.0.76' cat << MCPCONFIG_EOF | bash /opt/gh-aw/actions/start_mcp_gateway.sh { @@ -770,7 +1112,7 @@ jobs: To create or modify GitHub resources (issues, discussions, pull requests, etc.), you MUST call the appropriate safe output tool. Simply writing content will NOT work - the workflow requires actual tool calls. - **Available tools**: add_comment, add_labels, create_discussion, create_issue, missing_tool, noop + **Available tools**: add_comment, add_labels, create_issue, missing_tool, noop **Critical**: Tool calls write structured data that downstream jobs process. Without tool calls, follow-up actions will be skipped. @@ -827,6 +1169,8 @@ jobs: + + # Smoke Test: OpenCode Custom Engine Validation **IMPORTANT: Keep all outputs extremely short and concise. Use single-line responses where possible. No verbose explanations.** @@ -839,7 +1183,7 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Creation Testing**: Create a new discussion in __GH_AW_GITHUB_REPOSITORY__ with a space/rocket-themed title about the smoke test (be creative and use space mission language) + 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment to it stating that the smoke test agent was here (be creative and use space mission language) ## Output @@ -856,7 +1200,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **space/rocket-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use space mission language) + 3. Add a **space/rocket-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use space mission language) If all tests pass, add the label `smoke-opencode` to the pull request. @@ -1499,7 +1843,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_discussion\":{\"close_older_discussions\":true,\"expires\":2,\"max\":1},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"hide_older_comments\":true,\"max\":2},\"add_labels\":{\"allowed\":[\"smoke-opencode\"]},\"create_issue\":{\"close_older_issues\":true,\"expires\":2,\"group\":true,\"max\":1},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/smoke-opencode.md b/.github/workflows/smoke-opencode.md index de6d8843bd..72ac4509e3 100644 --- a/.github/workflows/smoke-opencode.md +++ b/.github/workflows/smoke-opencode.md @@ -17,6 +17,7 @@ name: Smoke OpenCode imports: - shared/opencode.md - shared/gh.md + - shared/github-queries-safe-input.md strict: true sandbox: mcp: @@ -42,9 +43,6 @@ safe-outputs: expires: 2h group: true close-older-issues: true - create-discussion: - expires: 2h - close-older-discussions: true add-labels: allowed: [smoke-opencode] messages: @@ -67,7 +65,7 @@ timeout-minutes: 10 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-${{ github.run_id }}.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Creation Testing**: Create a new discussion in ${{ github.repository }} with a space/rocket-themed title about the smoke test (be creative and use space mission language) +7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment to it stating that the smoke test agent was here (be creative and use space mission language) ## Output @@ -84,6 +82,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **space/rocket-themed comment** to the created discussion mentioning that this is a smoke test validation (be creative and use space mission language) +3. Add a **space/rocket-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use space mission language) If all tests pass, add the label `smoke-opencode` to the pull request. From 1fa37c1c7a47e09d96c010ddeaafe505ef83ef52 Mon Sep 17 00:00:00 2001 From: GitHub Action Date: Thu, 22 Jan 2026 23:20:51 +0000 Subject: [PATCH 11/14] Add changeset [skip-ci] --- .changeset/patch-add-discussion-smoke-workflows.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/patch-add-discussion-smoke-workflows.md b/.changeset/patch-add-discussion-smoke-workflows.md index 8eda7cabe8..df25c52892 100644 --- a/.changeset/patch-add-discussion-smoke-workflows.md +++ b/.changeset/patch-add-discussion-smoke-workflows.md @@ -2,7 +2,9 @@ "gh-aw": patch --- -Add discussion creation and commenting to smoke workflows; deprecate the `discussion` flag and add a codemod to remove it. +Add discussion interaction to smoke workflows; deprecate `discussion` flag and add a codemod to remove it. -This change updates smoke workflows to create discussions and post themed comments, adds safe-output tooling for creating discussions, and includes a compiler change and codemod to migrate away from the deprecated `discussion` field. +Smoke workflows now query the latest discussion and post comments to both the discussion and PR to validate discussion functionality. The compiler now serializes the legacy `discussion` boolean into safe-outputs handler configs for compatibility. A codemod was added to automatically remove the deprecated `safe-outputs.add-comment.discussion: true` field from workflows. + +--- From 87095de8e330d3e08cc49aaeb7a35896e7a04986 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 23:34:37 +0000 Subject: [PATCH 12/14] Make smoke workflow prompts more specific about extracting discussion number and using add_comment - Updated test requirement #7/#8 in all smoke workflows with explicit 3-step instructions - Step 1: Use github-discussion-query with limit=1, jq=".[0]" to get latest discussion - Step 2: Extract the discussion number from the JSON result - Step 3: Use add_comment tool with discussion_number parameter to add themed comment - Updated output instructions to reference the extracted discussion_number - Applied to all 4 smoke workflows: copilot, claude, codex, opencode - Recompiled all workflows successfully Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/smoke-claude.lock.yml | 7 +++++-- .github/workflows/smoke-claude.md | 7 +++++-- .github/workflows/smoke-codex.lock.yml | 7 +++++-- .github/workflows/smoke-codex.md | 7 +++++-- .github/workflows/smoke-copilot.lock.yml | 7 +++++-- .github/workflows/smoke-copilot.md | 7 +++++-- .github/workflows/smoke-opencode.lock.yml | 7 +++++-- .github/workflows/smoke-opencode.md | 7 +++++-- .../templates/upgrade-agentic-workflows.md | 19 ++++++++++--------- 9 files changed, 50 insertions(+), 25 deletions(-) diff --git a/.github/workflows/smoke-claude.lock.yml b/.github/workflows/smoke-claude.lock.yml index 2020cf0c40..3b8534a97e 100644 --- a/.github/workflows/smoke-claude.lock.yml +++ b/.github/workflows/smoke-claude.lock.yml @@ -1454,7 +1454,10 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) + 8. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a fun, comic-book style comment stating that the smoke test agent was here ## Output @@ -1471,7 +1474,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **fun comic-book style comment** to the latest discussion mentioning that the smoke test was here (be playful and use comic-book language) + 3. Use the `add_comment` tool to add a **fun comic-book style comment** to the latest discussion (using the `discussion_number` you extracted in step 8) - be playful and use comic-book language like "💥 WHOOSH!" If all tests pass, add the label `smoke-claude` to the pull request. diff --git a/.github/workflows/smoke-claude.md b/.github/workflows/smoke-claude.md index 3989789e74..a3240b64c3 100644 --- a/.github/workflows/smoke-claude.md +++ b/.github/workflows/smoke-claude.md @@ -79,7 +79,10 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-claude-${{ github.run_id }}.txt` with content "Smoke test passed for Claude at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment, comic-book style) +8. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from ${{ github.repository }} + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a fun, comic-book style comment stating that the smoke test agent was here ## Output @@ -96,6 +99,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **fun comic-book style comment** to the latest discussion mentioning that the smoke test was here (be playful and use comic-book language) +3. Use the `add_comment` tool to add a **fun comic-book style comment** to the latest discussion (using the `discussion_number` you extracted in step 8) - be playful and use comic-book language like "💥 WHOOSH!" If all tests pass, add the label `smoke-claude` to the pull request. diff --git a/.github/workflows/smoke-codex.lock.yml b/.github/workflows/smoke-codex.lock.yml index 1adc1d3384..471ad46291 100644 --- a/.github/workflows/smoke-codex.lock.yml +++ b/.github/workflows/smoke-codex.lock.yml @@ -1436,7 +1436,10 @@ jobs: 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a mystical oracle-themed comment to it stating that the smoke test agent was here (be creative and use mystical language) + 8. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a mystical, oracle-themed comment stating that the smoke test agent was here ## Output @@ -1445,7 +1448,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - Add a **mystical oracle-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use mystical language) + Use the `add_comment` tool to add a **mystical oracle-themed comment** to the latest discussion (using the `discussion_number` you extracted in step 8) - be creative and use mystical language like "🔮 The ancient spirits stir..." If all tests pass, add the label `smoke-codex` to the pull request. diff --git a/.github/workflows/smoke-codex.md b/.github/workflows/smoke-codex.md index aaa674024a..c98f9518f1 100644 --- a/.github/workflows/smoke-codex.md +++ b/.github/workflows/smoke-codex.md @@ -76,7 +76,10 @@ timeout-minutes: 10 5. **Tavily Web Search Testing**: Use the Tavily MCP server to perform a web search for "GitHub Agentic Workflows" and verify that results are returned with at least one item 6. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-codex-${{ github.run_id }}.txt` with content "Smoke test passed for Codex at $(date)" (create the directory if it doesn't exist) 7. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -8. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a mystical oracle-themed comment to it stating that the smoke test agent was here (be creative and use mystical language) +8. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from ${{ github.repository }} + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a mystical, oracle-themed comment stating that the smoke test agent was here ## Output @@ -85,6 +88,6 @@ Add a **very brief** comment (max 5-10 lines) to the current pull request with: - ✅ or ❌ for each test result - Overall status: PASS or FAIL -Add a **mystical oracle-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use mystical language) +Use the `add_comment` tool to add a **mystical oracle-themed comment** to the latest discussion (using the `discussion_number` you extracted in step 8) - be creative and use mystical language like "🔮 The ancient spirits stir..." If all tests pass, add the label `smoke-codex` to the pull request. diff --git a/.github/workflows/smoke-copilot.lock.yml b/.github/workflows/smoke-copilot.lock.yml index 47d2b8e49d..e990314dc6 100644 --- a/.github/workflows/smoke-copilot.lock.yml +++ b/.github/workflows/smoke-copilot.lock.yml @@ -1344,7 +1344,10 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment) + 7. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a fun, playful comment stating that the smoke test agent was here ## Output @@ -1363,7 +1366,7 @@ jobs: - Overall status: PASS or FAIL - Mention the pull request author and any assignees - 3. Add a **fun and creative comment** to the latest discussion mentioning that the smoke test was here (be playful and entertaining) + 3. Use the `add_comment` tool to add a **fun and creative comment** to the latest discussion (using the `discussion_number` you extracted in step 7) - be playful and entertaining in your comment If all tests pass: - Add the label `smoke-copilot` to the pull request diff --git a/.github/workflows/smoke-copilot.md b/.github/workflows/smoke-copilot.md index 5a1f4aad86..a8e913aaae 100644 --- a/.github/workflows/smoke-copilot.md +++ b/.github/workflows/smoke-copilot.md @@ -79,7 +79,10 @@ strict: true 4. **Playwright Testing**: Use playwright to navigate to and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-copilot-${{ github.run_id }}.txt` with content "Smoke test passed for Copilot at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a fun comment to it stating that the smoke test agent was here (be creative and playful with the comment) +7. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from ${{ github.repository }} + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a fun, playful comment stating that the smoke test agent was here ## Output @@ -98,7 +101,7 @@ strict: true - Overall status: PASS or FAIL - Mention the pull request author and any assignees -3. Add a **fun and creative comment** to the latest discussion mentioning that the smoke test was here (be playful and entertaining) +3. Use the `add_comment` tool to add a **fun and creative comment** to the latest discussion (using the `discussion_number` you extracted in step 7) - be playful and entertaining in your comment If all tests pass: - Add the label `smoke-copilot` to the pull request diff --git a/.github/workflows/smoke-opencode.lock.yml b/.github/workflows/smoke-opencode.lock.yml index e19bb75760..3318176c08 100644 --- a/.github/workflows/smoke-opencode.lock.yml +++ b/.github/workflows/smoke-opencode.lock.yml @@ -1183,7 +1183,10 @@ jobs: 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-__GH_AW_GITHUB_RUN_ID__.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) - 7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment to it stating that the smoke test agent was here (be creative and use space mission language) + 7. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from __GH_AW_GITHUB_REPOSITORY__ + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a space/rocket-themed comment stating that the smoke test agent was here ## Output @@ -1200,7 +1203,7 @@ jobs: - ✅ or ❌ for each test result - Overall status: PASS or FAIL - 3. Add a **space/rocket-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use space mission language) + 3. Use the `add_comment` tool to add a **space/rocket-themed comment** to the latest discussion (using the `discussion_number` you extracted in step 7) - be creative and use space mission language like "🚀 IGNITION!" If all tests pass, add the label `smoke-opencode` to the pull request. diff --git a/.github/workflows/smoke-opencode.md b/.github/workflows/smoke-opencode.md index 72ac4509e3..fa4fc0f9ed 100644 --- a/.github/workflows/smoke-opencode.md +++ b/.github/workflows/smoke-opencode.md @@ -65,7 +65,10 @@ timeout-minutes: 10 4. **Playwright Testing**: Use playwright to navigate to https://github.com and verify the page title contains "GitHub" 5. **File Writing Testing**: Create a test file `/tmp/gh-aw/agent/smoke-test-opencode-${{ github.run_id }}.txt` with content "Smoke test passed for OpenCode at $(date)" (create the directory if it doesn't exist) 6. **Bash Tool Testing**: Execute bash commands to verify file creation was successful (use `cat` to read the file back) -7. **Discussion Interaction Testing**: Use the `github-discussion-query` safe-input tool to get the latest discussion from ${{ github.repository }} (use params: limit=1, jq=".[0]") and add a space/rocket-themed comment to it stating that the smoke test agent was here (be creative and use space mission language) +7. **Discussion Interaction Testing**: + - Use the `github-discussion-query` safe-input tool with params: `limit=1, jq=".[0]"` to get the latest discussion from ${{ github.repository }} + - Extract the discussion number from the result (e.g., if the result is `{"number": 123, "title": "...", ...}`, extract 123) + - Use the `add_comment` tool with `discussion_number: ` to add a space/rocket-themed comment stating that the smoke test agent was here ## Output @@ -82,6 +85,6 @@ timeout-minutes: 10 - ✅ or ❌ for each test result - Overall status: PASS or FAIL -3. Add a **space/rocket-themed comment** to the latest discussion mentioning that the smoke test was here (be creative and use space mission language) +3. Use the `add_comment` tool to add a **space/rocket-themed comment** to the latest discussion (using the `discussion_number` you extracted in step 7) - be creative and use space mission language like "🚀 IGNITION!" If all tests pass, add the label `smoke-opencode` to the pull request. diff --git a/pkg/cli/templates/upgrade-agentic-workflows.md b/pkg/cli/templates/upgrade-agentic-workflows.md index b05cb67671..b278e47797 100644 --- a/pkg/cli/templates/upgrade-agentic-workflows.md +++ b/pkg/cli/templates/upgrade-agentic-workflows.md @@ -23,15 +23,16 @@ Read the ENTIRE content of this file carefully before proceeding. Follow the ins - `compile` → compile all workflows - `compile ` → compile a specific workflow -:::note[Command Execution] -When running in GitHub Copilot Cloud, you don't have direct access to `gh aw` CLI commands. Instead, use the **agentic-workflows** MCP tool: -- `fix` tool → apply automatic codemods to fix deprecated fields -- `compile` tool → compile workflows - -When running in other environments with `gh aw` CLI access, prefix commands with `gh aw` (e.g., `gh aw compile`). - -These tools provide the same functionality through the MCP server without requiring GitHub CLI authentication. -::: +> [!NOTE] +> **Command Execution** +> +> When running in GitHub Copilot Cloud, you don't have direct access to `gh aw` CLI commands. Instead, use the **agentic-workflows** MCP tool: +> - `fix` tool → apply automatic codemods to fix deprecated fields +> - `compile` tool → compile workflows +> +> When running in other environments with `gh aw` CLI access, prefix commands with `gh aw` (e.g., `gh aw compile`). +> +> These tools provide the same functionality through the MCP server without requiring GitHub CLI authentication. ## Instructions From 0b26643ba72048fc372f222cb744c872959d085e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 23:42:07 +0000 Subject: [PATCH 13/14] Remove deprecated discussion flag from compiler output and recompile all workflows - Updated compiler_safe_outputs_config.go to NOT emit the discussion flag to handler config - Added comment explaining that discussion support is always available in add_comment handler - Recompiled all 137 workflows successfully - Verified daily-fact.lock.yml no longer contains "discussion":true in handler config - The discussion flag is deprecated; add_comment handler auto-detects discussion targets Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/daily-fact.lock.yml | 2 +- pkg/workflow/compiler_safe_outputs_config.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index 1f391ecb4e..7b9ab9e671 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -1052,7 +1052,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 env: GH_AW_AGENT_OUTPUT: ${{ env.GH_AW_AGENT_OUTPUT }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"discussion\":true,\"max\":1,\"target\":\"4750\"},\"missing_data\":{},\"missing_tool\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":1,\"target\":\"4750\"},\"missing_data\":{},\"missing_tool\":{}}" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/pkg/workflow/compiler_safe_outputs_config.go b/pkg/workflow/compiler_safe_outputs_config.go index 3680e5c089..e5ecce541c 100644 --- a/pkg/workflow/compiler_safe_outputs_config.go +++ b/pkg/workflow/compiler_safe_outputs_config.go @@ -81,10 +81,8 @@ func (c *Compiler) addHandlerManagerConfigEnvVar(steps *[]string, data *Workflow if len(cfg.AllowedRepos) > 0 { handlerConfig["allowed_repos"] = cfg.AllowedRepos } - // Add discussion flag to config - if cfg.Discussion != nil && *cfg.Discussion { - handlerConfig["discussion"] = true - } + // Note: discussion flag is deprecated and not emitted to config + // Discussion support is always available in add_comment handler config["add_comment"] = handlerConfig } From 86c7c36e48c1d61fd6506a12f645e869ef7e1088 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 23 Jan 2026 01:29:07 +0000 Subject: [PATCH 14/14] Add changeset [skip-ci] --- .changeset/patch-add-discussion-smoke-workflows.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.changeset/patch-add-discussion-smoke-workflows.md b/.changeset/patch-add-discussion-smoke-workflows.md index df25c52892..df62950865 100644 --- a/.changeset/patch-add-discussion-smoke-workflows.md +++ b/.changeset/patch-add-discussion-smoke-workflows.md @@ -2,9 +2,12 @@ "gh-aw": patch --- -Add discussion interaction to smoke workflows; deprecate `discussion` flag and add a codemod to remove it. - -Smoke workflows now query the latest discussion and post comments to both the discussion and PR to validate discussion functionality. The compiler now serializes the legacy `discussion` boolean into safe-outputs handler configs for compatibility. A codemod was added to automatically remove the deprecated `safe-outputs.add-comment.discussion: true` field from workflows. - ---- +Add discussion interaction to smoke workflows; deprecate the `discussion` flag and +add a codemod to remove it. Smoke workflows now query discussions and post +comments to both discussions and PRs to validate discussion functionality. + +The compiler no longer emits a `discussion` boolean flag in compiled handler +configs; the `add_comment` handler auto-detects target type or accepts a +`discussion_number` parameter. A codemod `add-comment-discussion-removal` is +available via `gh aw fix --write` to remove the deprecated field from workflows.