diff --git a/.github/workflows/review-responder.lock.yml b/.github/workflows/review-responder.lock.yml index 26e31f2..4e7b882 100644 --- a/.github/workflows/review-responder.lock.yml +++ b/.github/workflows/review-responder.lock.yml @@ -22,40 +22,34 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # -# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"1f273805d83429b3a54dcfbae6be92e73d936e61f45a40685d07d6aea98ec2ad","compiler_version":"v0.60.0","strict":true} +# gh-aw-metadata: {"schema_version":"v2","frontmatter_hash":"e9dedf9ee2f5a6beffd74f5c1ac40c9263eda4f41e3db146c13a4d8576fd3970","compiler_version":"v0.60.0","strict":true} name: "Review Responder" "on": - # bots: # Bots processed as bot check in pre-activation job - # - Copilot # Bots processed as bot check in pre-activation job - # - copilot-pull-request-reviewer # Bots processed as bot check in pre-activation job - pull_request_review: - types: - - submitted - # roles: all # Roles processed as role check in pre-activation job + workflow_dispatch: + inputs: + pr_number: + description: PR number to address review comments on + required: true + type: string permissions: {} concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref || github.run_id }}" - cancel-in-progress: true + group: "gh-aw-${{ github.workflow }}" run-name: "Review Responder" jobs: activation: - if: contains(github.event.pull_request.labels.*.name, 'aw') runs-on: ubuntu-slim permissions: contents: read outputs: - body: ${{ steps.sanitized.outputs.body }} comment_id: "" comment_repo: "" model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} - text: ${{ steps.sanitized.outputs.text }} - title: ${{ steps.sanitized.outputs.title }} steps: - name: Setup Scripts uses: github/gh-aw-actions/setup@v0.60.0 @@ -109,17 +103,6 @@ jobs: setupGlobals(core, github, context, exec, io); const { main } = require('/opt/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - - name: Compute current body text - id: sanitized - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 - env: - GH_AW_ALLOWED_BOTS: Copilot,copilot-pull-request-reviewer - with: - script: | - const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('/opt/gh-aw/actions/compute_text.cjs'); - await main(); - name: Create prompt with built-in context env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -132,7 +115,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} run: | bash /opt/gh-aw/actions/create_prompt_first.sh { @@ -176,13 +159,13 @@ jobs: {{#if __GH_AW_GITHUB_RUN_ID__ }} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} + - **checkouts**: The following repositories have been checked out and are available in the workspace: + - `$GITHUB_WORKSPACE` → `__GH_AW_GITHUB_REPOSITORY__` (cwd) [full history, all branches available as remote-tracking refs] [additional refs fetched: *] + - **Note**: If a branch you need is not in the list above and is not listed as an additional fetched ref, it has NOT been checked out. For private repositories you cannot fetch it without proper authentication. If the branch is required and not available, exit with an error and ask the user to add it to the `fetch:` option of the `checkout:` configuration (e.g., `fetch: ["refs/pulls/open/*"]` for all open PR refs, or `fetch: ["main", "feature/my-branch"]` for specific branches). GH_AW_PROMPT_EOF cat "/opt/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then - cat "/opt/gh-aw/prompts/pr_context_prompt.md" - fi cat << 'GH_AW_PROMPT_EOF' GH_AW_PROMPT_EOF @@ -194,7 +177,7 @@ jobs: uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -213,7 +196,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_INPUTS_PR_NUMBER: ${{ inputs.pr_number }} with: script: | const { setupGlobals } = require('/opt/gh-aw/actions/setup_globals.cjs'); @@ -233,7 +216,7 @@ jobs: GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT + GH_AW_INPUTS_PR_NUMBER: process.env.GH_AW_INPUTS_PR_NUMBER } }); - name: Validate prompt placeholders @@ -289,6 +272,13 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false + fetch-depth: 0 + - name: Fetch additional refs + env: + GH_AW_FETCH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + header=$(printf "x-access-token:%s" "${GH_AW_FETCH_TOKEN}" | base64 -w 0) + git -c "http.extraheader=Authorization: Basic ${header}" fetch origin '+refs/heads/*:refs/remotes/origin/*' - name: Create gh-aw temp directory run: bash /opt/gh-aw/actions/create_gh_aw_tmp_dir.sh - name: Configure Git credentials @@ -341,7 +331,7 @@ jobs: mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs cat > /opt/gh-aw/safeoutputs/config.json << 'GH_AW_SAFE_OUTPUTS_CONFIG_EOF' - {"add_labels":{"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{"max":1},"reply_to_pull_request_review_comment":{"max":10}} + {"add_labels":{"max":3},"missing_data":{},"missing_tool":{},"noop":{"max":1},"push_to_pull_request_branch":{"max":1,"target":"*"},"reply_to_pull_request_review_comment":{"max":10,"target":"*"}} GH_AW_SAFE_OUTPUTS_CONFIG_EOF - name: Write Safe Outputs Tools run: | @@ -1070,7 +1060,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.pythonhosted.org,anaconda.org,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,binstar.org,bootstrap.pypa.io,conda.anaconda.org,conda.binstar.org,crates.io,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,files.pythonhosted.org,github.com,host.docker.internal,index.crates.io,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pip.pypa.io,ppa.launchpad.net,pypi.org,pypi.python.org,raw.githubusercontent.com,registry.npmjs.org,repo.anaconda.com,repo.continuum.io,s.symcb.com,s.symcd.com,security.ubuntu.com,static.crates.io,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\"},\"missing_data\":{},\"missing_tool\":{},\"push_to_pull_request_branch\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\",\"if_no_changes\":\"warn\",\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"]},\"reply_to_pull_request_review_comment\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\",\"max\":10}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_labels\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\"},\"missing_data\":{},\"missing_tool\":{},\"push_to_pull_request_branch\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\",\"if_no_changes\":\"warn\",\"labels\":[\"aw\"],\"max_patch_size\":1024,\"protected_files\":[\"package.json\",\"bun.lockb\",\"bunfig.toml\",\"deno.json\",\"deno.jsonc\",\"deno.lock\",\"global.json\",\"NuGet.Config\",\"Directory.Packages.props\",\"mix.exs\",\"mix.lock\",\"go.mod\",\"go.sum\",\"stack.yaml\",\"stack.yaml.lock\",\"pom.xml\",\"build.gradle\",\"build.gradle.kts\",\"settings.gradle\",\"settings.gradle.kts\",\"gradle.properties\",\"package-lock.json\",\"yarn.lock\",\"pnpm-lock.yaml\",\"npm-shrinkwrap.json\",\"requirements.txt\",\"Pipfile\",\"Pipfile.lock\",\"pyproject.toml\",\"setup.py\",\"setup.cfg\",\"Gemfile\",\"Gemfile.lock\",\"uv.lock\",\"AGENTS.md\"],\"protected_path_prefixes\":[\".github/\",\".agents/\"],\"target\":\"*\"},\"reply_to_pull_request_review_comment\":{\"github-token\":\"${{ secrets.GH_AW_WRITE_TOKEN }}\",\"max\":10,\"target\":\"*\"}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} GITHUB_TOKEN: ${{ secrets.GH_AW_WRITE_TOKEN }} with: diff --git a/.github/workflows/review-responder.md b/.github/workflows/review-responder.md index 9fa6920..c0d1a44 100644 --- a/.github/workflows/review-responder.md +++ b/.github/workflows/review-responder.md @@ -1,16 +1,21 @@ --- -if: "contains(github.event.pull_request.labels.*.name, 'aw')" on: - pull_request_review: - types: [submitted] - roles: all - bots: [Copilot, copilot-pull-request-reviewer] + workflow_dispatch: + inputs: + pr_number: + description: "PR number to address review comments on" + required: true + type: string permissions: contents: read issues: read pull-requests: read +checkout: + fetch: ["*"] + fetch-depth: 0 + engine: id: copilot model: claude-opus-4.6 @@ -28,8 +33,11 @@ safe-outputs: noop: report-as-issue: false push-to-pull-request-branch: + target: "*" + labels: [aw] github-token: ${{ secrets.GH_AW_WRITE_TOKEN }} reply-to-pull-request-review-comment: + target: "*" max: 10 github-token: ${{ secrets.GH_AW_WRITE_TOKEN }} add-labels: @@ -39,32 +47,28 @@ safe-outputs: # Review Responder -Address review comments on pull request #${{ github.event.pull_request.number }}. +Address review comments on pull request #${{ inputs.pr_number }}. ## Instructions -This workflow runs when a review is submitted on a pull request. - -1. First, check if the PR has the `aw` label. If it does NOT have the `aw` label, stop immediately — this workflow only handles agent-created PRs. - -2. Check the review that triggered this workflow. If the review has no comments (e.g., a plain approval with no inline comments), stop — there is nothing to address. +This workflow addresses unresolved review comments on a pull request. -3. Check if the PR already has the label `review-response-attempted`. If it does, add a comment to the PR saying "Review response already attempted — stopping to prevent loops. Manual intervention needed." and stop. +1. Check if the PR already has the label `review-response-attempted`. If it does, add a comment to the PR saying "Review response already attempted — stopping to prevent loops. Manual intervention needed." and stop. -4. Add the label `review-response-attempted` to the PR. +2. Add the label `review-response-attempted` to the PR. -5. Read the unresolved review comment threads on the PR (not just the latest review — get all unresolved threads). If there are more than 10 unresolved threads, address the first 10 and leave a summary comment on the PR noting how many remain for manual follow-up. +3. Read the unresolved review comment threads on the PR (not just the latest review — get all unresolved threads). If there are more than 10 unresolved threads, address the first 10 and leave a summary comment on the PR noting how many remain for manual follow-up. -6. For each unresolved review comment thread (up to 10): +4. For each unresolved review comment thread (up to 10): a. Read the comment and understand what change is being requested b. Read the relevant file and surrounding code context - c. Make the requested fix in the code (edit the file locally — do NOT push yet) + c. Make the requested fix in the code d. Reply to the comment thread explaining what you changed -7. After addressing all comments, run the CI checks locally to make sure your fixes don't break anything: `uv sync && uv run ruff check --fix . && uv run ruff format . && uv run pyright && uv run pytest --cov --cov-fail-under=80 -v` +5. After addressing all comments, run the CI checks locally to make sure your fixes don't break anything: `uv sync && uv run ruff check --fix . && uv run ruff format . && uv run pyright && uv run pytest --cov --cov-fail-under=80 -v` -8. Push all changes in a single commit with message "fix: address review comments". Reply to all threads BEFORE pushing — replies after a push will appear on outdated code. +6. If CI checks fail, fix the issues and re-run until they pass. Do not push broken code. -If a review comment requests a change that would be architecturally significant or you're unsure about, reply to the thread explaining your concern rather than making the change blindly. +7. Push all changes in a single commit with message "fix: address review comments". -NOTE: Thread resolution is handled separately by the pipeline orchestrator after this workflow completes. Your job is to fix the code and reply to threads — do NOT attempt to resolve threads. +If a review comment requests a change that would be architecturally significant or you're unsure about, reply to the thread explaining your concern rather than making the change blindly. diff --git a/tests/test_responder_sandbox.py b/tests/test_responder_sandbox.py new file mode 100644 index 0000000..7057f71 --- /dev/null +++ b/tests/test_responder_sandbox.py @@ -0,0 +1,16 @@ +# Responder Sandbox — intentionally imperfect code for testing + + +def calculate_total(lst: list[int]) -> int: + total = 0 + for i in lst: + total = total + i + return total + + +def format_output(data: list[str], verbose: bool) -> str: + result = "" + for item in data: + suffix = "\n" if verbose else ", " + result = result + str(item) + suffix + return result