diff --git a/.github/workflows/pr-sous-chef.lock.yml b/.github/workflows/pr-sous-chef.lock.yml index d10c151c351..94cdde70334 100644 --- a/.github/workflows/pr-sous-chef.lock.yml +++ b/.github/workflows/pr-sous-chef.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"64e4ac44d478d1ff1d5cbaa050f7412ea0a0745218ebd4910d2e0d68ae2e3696","body_hash":"7789d855cf7ad1e35be8d5cdff757761bdb06bddc7ecb843258336d8fbd6c7a9","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.3"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"9cd58a3cfd50f2fff17e4ecfa36f3eb1a796c466578fdaf17d1b5974b2ff83b9","body_hash":"5435e9fb02ce343b3a0921b33e17ff5ffc04220cff10c62eb543aa2057d24ace","strict":true,"agent_id":"pi","agent_model":"copilot/gpt-5.4","engine_versions":{"pi":"0.80.3"}} # gh-aw-manifest: {"version":1,"secrets":["AWI_MAINTENANCE_TOKEN","COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GH_AW_OTEL_GRAFANA_AUTHORIZATION","GH_AW_OTEL_GRAFANA_ENDPOINT","GH_AW_OTEL_SENTRY_AUTHORIZATION","GH_AW_OTEL_SENTRY_ENDPOINT","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/cache/save","sha":"55cc8345863c7cc4c66a329aec7e433d2d1c52a9","version":"v6.1.0"},{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-go","sha":"924ae3a1cded613372ab5595356fb5720e22ba16","version":"v6.5.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.29","digest":"sha256:debc0b18ef8ea3a64585c4d1eea1099f0d9fa76b53d34a1f3c53b3225fe158fe","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.29@sha256:debc0b18ef8ea3a64585c4d1eea1099f0d9fa76b53d34a1f3c53b3225fe158fe"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.29","digest":"sha256:c7754df3f06f346c817db0525ba523cbdaf5349239fd7f37897c4250a8fc7bde","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.29@sha256:c7754df3f06f346c817db0525ba523cbdaf5349239fd7f37897c4250a8fc7bde"},{"image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.29","digest":"sha256:bfc90b1f10d2ff61dbbbf0d57600001f85bf5f6444ed78bc05d9f6677327e4b8","pinned_image":"ghcr.io/github/gh-aw-firewall/cli-proxy:0.27.29@sha256:bfc90b1f10d2ff61dbbbf0d57600001f85bf5f6444ed78bc05d9f6677327e4b8"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.29","digest":"sha256:7bfa0742f9a5bd6309507caaa80a8b6cf3e05bd95a1429affbf64cc94cfbd34f","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.29@sha256:7bfa0742f9a5bd6309507caaa80a8b6cf3e05bd95a1429affbf64cc94cfbd34f"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.4.1","digest":"sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.4.1@sha256:ad2a979c2cd8b50098e84938ca9c9c1580eb8e91526f101a90adfba7859b2c32"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.5.0","digest":"sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4","pinned_image":"ghcr.io/github/github-mcp-server:v1.5.0@sha256:e25564dccc9110a70a77b9df560cbde11aa392fcb5f08b9abe5c4ebc6d146ea4"}]} # This file was automatically generated by gh-aw. DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # @@ -316,23 +316,23 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_1007377445313f3f_EOF' + cat << 'GH_AW_PROMPT_09586fda74ac0644_EOF' - GH_AW_PROMPT_1007377445313f3f_EOF + GH_AW_PROMPT_09586fda74ac0644_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_1007377445313f3f_EOF' + cat << 'GH_AW_PROMPT_09586fda74ac0644_EOF' - Tools: add_comment(max:20), create_issue, update_pull_request(max:10), dismiss_pull_request_review(max:20), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop - GH_AW_PROMPT_1007377445313f3f_EOF + Tools: add_comment(max:4), create_issue, update_pull_request(max:10), dismiss_pull_request_review(max:20), resolve_pull_request_review_thread(max:40), push_to_pull_request_branch(max:10), missing_tool, missing_data, noop + GH_AW_PROMPT_09586fda74ac0644_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_push_to_pr_branch.md" - cat << 'GH_AW_PROMPT_1007377445313f3f_EOF' + cat << 'GH_AW_PROMPT_09586fda74ac0644_EOF' - GH_AW_PROMPT_1007377445313f3f_EOF + GH_AW_PROMPT_09586fda74ac0644_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_1007377445313f3f_EOF' + cat << 'GH_AW_PROMPT_09586fda74ac0644_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -374,7 +374,7 @@ jobs: stop immediately and report the limitation rather than spending turns trying to work around it. - GH_AW_PROMPT_1007377445313f3f_EOF + GH_AW_PROMPT_09586fda74ac0644_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/cli_proxy_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 "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" @@ -382,11 +382,11 @@ jobs: 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 "${RUNNER_TEMP}/gh-aw/prompts/pr_context_push_to_pr_branch_guidance.md" fi - cat << 'GH_AW_PROMPT_1007377445313f3f_EOF' + cat << 'GH_AW_PROMPT_09586fda74ac0644_EOF' {{#runtime-import .github/workflows/shared/otlp.md}} {{#runtime-import .github/workflows/pr-sous-chef.md}} - GH_AW_PROMPT_1007377445313f3f_EOF + GH_AW_PROMPT_09586fda74ac0644_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -830,18 +830,19 @@ jobs: mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_fd43ee3d7dd4e34f_EOF' - {"add_comment":{"github-token":"${GH_AW_SECRET_AWI_MAINTENANCE_TOKEN}","max":20,"target":"*"},"create_issue":{"close_older_issues":true,"expires":72,"labels":["automation"],"max":1,"title_prefix":"[pr-sous-chef] "},"create_report_incomplete_issue":{},"dismiss_pull_request_review":{"max":20,"target":"*"},"mentions":{"allowed":["copilot"]},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"commit_title_suffix":" [pr-sous-chef]","excluded_files":[".github/workflows/**"],"if_no_changes":"ignore","max":10,"max_patch_size":4096,"protect_top_level_dot_folders":true,"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","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","PI.md","AGENTS.md"],"target":"*"},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":10,"target":"*","update_branch":true}} - GH_AW_SAFE_OUTPUTS_CONFIG_fd43ee3d7dd4e34f_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_fb1d6d2d5c7b3fe7_EOF' + {"add_comment":{"github-token":"${GH_AW_SECRET_AWI_MAINTENANCE_TOKEN}","max":4,"target":"*"},"create_issue":{"close_older_issues":true,"expires":72,"labels":["automation"],"max":1,"title_prefix":"[pr-sous-chef] "},"create_report_incomplete_issue":{},"dismiss_pull_request_review":{"max":20,"target":"*"},"mentions":{"allowed":["copilot"]},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"commit_title_suffix":" [pr-sous-chef]","excluded_files":[".github/workflows/**"],"if_no_changes":"ignore","max":10,"max_patch_size":4096,"protect_top_level_dot_folders":true,"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","CODEOWNERS","DESIGN.md","README.md","CONTRIBUTING.md","CHANGELOG.md","SECURITY.md","CODE_OF_CONDUCT.md","PI.md","AGENTS.md"],"target":"*"},"report_incomplete":{},"resolve_pull_request_review_thread":{"max":40},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":10,"target":"*","update_branch":true}} + GH_AW_SAFE_OUTPUTS_CONFIG_fb1d6d2d5c7b3fe7_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "add_comment": " CONSTRAINTS: Maximum 20 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", + "add_comment": " CONSTRAINTS: Maximum 4 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[pr-sous-chef] \". Labels [\"automation\"] will be automatically added.", "dismiss_pull_request_review": " CONSTRAINTS: Maximum 20 review dismissal(s) can be performed. Target: *. justification must contain at least 20 characters.", "push_to_pull_request_branch": " CONSTRAINTS: Maximum 10 push(es) can be made.", + "resolve_pull_request_review_thread": " CONSTRAINTS: Maximum 40 review thread(s) can be resolved.", "update_pull_request": " CONSTRAINTS: Maximum 10 pull request(s) can be updated. Target: *." }, "repo_params": {}, @@ -1033,6 +1034,15 @@ jobs: } } }, + "resolve_pull_request_review_thread": { + "defaultMax": 10, + "fields": { + "thread_id": { + "required": true, + "type": "string" + } + } + }, "update_pull_request": { "defaultMax": 1, "fields": { @@ -2135,7 +2145,7 @@ jobs: GH_AW_ALLOWED_DOMAINS: "*.grafana.net,*.sentry.io,api.githubcopilot.com,api.pi.ai,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,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,github.com,go.dev,golang.org,goproxy.io,host.docker.internal,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,pkg.go.dev,ppa.launchpad.net,proxy.golang.org,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,storage.googleapis.com,sum.golang.org,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.AWI_MAINTENANCE_TOKEN }}\",\"max\":20,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"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\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"github-token\":\"${{ secrets.AWI_MAINTENANCE_TOKEN }}\",\"max\":4,\"target\":\"*\"},\"create_issue\":{\"close_older_issues\":true,\"expires\":72,\"labels\":[\"automation\"],\"max\":1,\"title_prefix\":\"[pr-sous-chef] \"},\"create_report_incomplete_issue\":{},\"dismiss_pull_request_review\":{\"max\":20,\"target\":\"*\"},\"mentions\":{\"allowed\":[\"copilot\"]},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"push_to_pull_request_branch\":{\"commit_title_suffix\":\" [pr-sous-chef]\",\"excluded_files\":[\".github/workflows/**\"],\"if_no_changes\":\"ignore\",\"max\":10,\"max_patch_size\":4096,\"protect_top_level_dot_folders\":true,\"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\",\"CODEOWNERS\",\"DESIGN.md\",\"README.md\",\"CONTRIBUTING.md\",\"CHANGELOG.md\",\"SECURITY.md\",\"CODE_OF_CONDUCT.md\",\"PI.md\",\"AGENTS.md\"],\"target\":\"*\"},\"report_incomplete\":{},\"resolve_pull_request_review_thread\":{\"max\":40},\"update_pull_request\":{\"allow_body\":true,\"allow_title\":false,\"default_operation\":\"append\",\"max\":10,\"target\":\"*\",\"update_branch\":true}}" GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr-sous-chef.md b/.github/workflows/pr-sous-chef.md index 5b0badd93aa..1769f1c5661 100644 --- a/.github/workflows/pr-sous-chef.md +++ b/.github/workflows/pr-sous-chef.md @@ -208,9 +208,11 @@ steps: run: npm ci --prefix actions/setup/js safe-outputs: add-comment: - max: 20 + max: 4 target: "*" github-token: ${{ secrets.AWI_MAINTENANCE_TOKEN }} + resolve-pull-request-review-thread: + max: 40 dismiss-pull-request-review: max: 20 target: "*" @@ -266,12 +268,18 @@ When this workflow is triggered by the `/souschef` slash command on a PR comment 1. Read `/tmp/gh-aw/agent/pr-sous-chef-candidates-compact.json` first. 2. If `prs` is empty, create the run-report issue (see **Run summary** below) and stop. If `create_issue` is unavailable, fall back to `noop` with the message `"processed=0; nudged=0; no eligible PRs"` and stop. 3. Process PRs in `updatedAt` descending order. -4. Process all eligible PRs per run. -5. Use the `pr-processor` sub-agent for each PR; pass only the PR number and compact context. -6. If a `pr-processor` call returns non-JSON or an error, record `{pr_number: , skip_reason: "sub_agent_error"}` in the `skipped` array of the run-summary issue payload and move to the next PR without retrying. -7. Do not fetch full PR diffs or large file lists unless absolutely required for a skip decision. -8. **Never finish without at least one safe-output tool call.** Always call the run-summary `create_issue` (see **Run summary** below) before finishing. If `create_issue` is unavailable, fall back to `noop` with a condensed message containing the run counts (see fallback example in **Run summary**). -9. Use `safeoutputs --param value` shell commands for all safe-output operations (`add_comment`, `update_pull_request`, `push_to_pull_request_branch`, `create_issue`, `noop`, `report_incomplete`). Do **not** use `gh pr comment`, `gh pr update-branch`, `gh api ... -X POST`, or any GitHub API write calls outside of `safeoutputs`. Do **not** pipe `safeoutputs` to other commands or run `safeoutputs --help` — the tool schemas are already provided; use the examples below directly. +4. Process at most 4 nudges per run. +5. Prioritize which PRs to nudge, in this order: + - `mergeStateStatus == "CONFLICTING"` first (explicit merge-conflict unblock request). + - PRs with unresolved review threads where at least one thread already has a follow-up response from the PR author or `@copilot` but remains unresolved. + - Remaining PRs by most-recent `updatedAt`. + If two PRs are still tied, prioritize the lower PR number first for deterministic behavior and stable reruns. +6. After applying skip rules, stop creating new nudge comments once 4 PRs have been nudged in the current run. Continue processing only for required bookkeeping/reporting. +7. Use the `pr-processor` sub-agent for each PR; pass only the PR number and compact context. +8. If a `pr-processor` call returns non-JSON or an error, record `{pr_number: , skip_reason: "sub_agent_error"}` in the `skipped` array of the run-summary issue payload and move to the next PR without retrying. +9. Do not fetch full PR diffs or large file lists unless absolutely required for a skip decision. +10. **Never finish without at least one safe-output tool call.** Always call the run-summary `create_issue` (see **Run summary** below) before finishing. If `create_issue` is unavailable, fall back to `noop` with a condensed message containing the run counts (see fallback example in **Run summary**). +11. Use `safeoutputs --param value` shell commands for all safe-output operations (`add_comment`, `update_pull_request`, `push_to_pull_request_branch`, `resolve_pull_request_review_thread`, `dismiss_pull_request_review`, `create_issue`, `noop`, `report_incomplete`). Do **not** use `gh pr comment`, `gh pr update-branch`, `gh api ... -X POST`, or any GitHub API write calls outside of `safeoutputs`. Do **not** pipe `safeoutputs` to other commands or run `safeoutputs --help` — the tool schemas are already provided; use the examples below directly. ## Required skip rules per PR @@ -294,7 +302,7 @@ Before any nudge for a PR: - Candidate prefilter already removes PRs where the most recent sous-chef comment (containing both the marker and `@copilot`) was posted within the last 30 minutes. - If any recent comment contains both `` and `@copilot` and was created less than 30 minutes ago, skip this PR. Comments with the marker but without `@copilot` are informational and do **not** trigger the cooldown. -## Required nudges for eligible PRs +## Required nudges for prioritized eligible PRs For each PR that is not skipped: @@ -337,9 +345,21 @@ For each PR that is not skipped: safeoutputs add_comment --pr_number 12345 --body $'\n@copilot please run the `pr-finisher` skill, address unresolved review comments, and rerun checks once the branch is up to date.' ``` -3. **Dismiss stale `github-actions[bot]` blocking reviews when all PR review threads are resolved** +3. **Resolve review threads that already have a response using a safe output** + - For `schedule` and `workflow_dispatch` runs, use the `resolve_review_threads` list returned by the `pr-processor` sub-agent. + - Include a thread in `resolve_review_threads` only when all of the following are true: + - the thread is currently unresolved; + - the thread contains reviewer feedback; + - a later reply in that same thread exists from the PR author or `@copilot` addressing the feedback. + - For each thread ID in `resolve_review_threads`, call: + ```bash + safeoutputs resolve_pull_request_review_thread --thread_id PRRT_kwDOABCD1234 + ``` + - If resolving one thread fails, append `{pr_number: , skip_reason: "resolve_review_thread_failed", thread_id: ""}` to the run-summary `skipped` array and continue with remaining thread IDs; do not fail the run solely because one resolution attempt failed. + +4. **Dismiss stale `github-actions[bot]` blocking reviews when all PR review threads are resolved** - **Slash-command guard**: If triggered via the `/souschef` slash command (`pull_request_comment` event), skip this dismissal step entirely — slash-command runs are acknowledgment nudges and must not perform automated review cleanup. - - For `schedule` and `workflow_dispatch` runs, use the `dismiss_reviews` list returned by the `pr-processor` sub-agent. The sub-agent populates this list only when ALL review threads on the PR are resolved; leave reviews untouched if any thread remains unresolved. + - For `schedule` and `workflow_dispatch` runs, use the `dismiss_reviews` list returned by the `pr-processor` sub-agent. The sub-agent populates this list only when ALL review threads on the PR are resolved (including threads resolved in step 3); leave reviews untouched if any thread remains unresolved. - `dismiss_pull_request_review` uses the `GITHUB_TOKEN`, which is always authenticated as `github-actions[bot]` regardless of the workflow trigger. It can therefore dismiss `github-actions[bot]`-authored reviews on any non-slash-command run. - For each review ID in `dismiss_reviews`, call the native safe-output tool: ```bash @@ -370,6 +390,7 @@ Then include the run counts as a compact table: | branch_update_attempts | N | | formatter_pushes | N | | merge_main_scheduled | N | +| resolved_review_threads | N | | dismissed_reviews | N | If any PRs were nudged, include a collapsible list of their numbers and titles. @@ -380,7 +401,7 @@ Example (`create_issue` shell call): safeoutputs create_issue --title "Run report — 2 nudged, 1 skipped" --body $'\n> ⚠️ **This is an automated status report. Do not assign this issue to a Copilot agent.**\n\n...' ``` -If `create_issue` is unavailable, fall back to `noop` with a condensed message containing the run counts, e.g. `"processed=4; skipped_checks_running=0; skipped_last_comment_from_sous_chef=1; skipped_cooldown=1; nudged=2; branch_update_attempts=0; formatter_pushes=0; merge_main_scheduled=1; dismissed_reviews=1"`. +If `create_issue` is unavailable, fall back to `noop` with a condensed message containing the run counts, e.g. `"processed=4; skipped_checks_running=0; skipped_last_comment_from_sous_chef=1; skipped_cooldown=1; nudged=2; branch_update_attempts=0; formatter_pushes=0; merge_main_scheduled=1; resolved_review_threads=3; dismissed_reviews=1"`. ## Formatting Requirements @@ -413,6 +434,7 @@ Given one PR number and compact metadata: - a single combined nudge comment body: - if `conflicting` is true: a targeted nudge asking `@copilot` to run `make merge-main` to resolve conflicts - otherwise: a combined nudge covering unresolved review feedback, branch refresh, and any other forward-progress action including a direct instruction to run the `pr-finisher` skill — one comment only, never two; if unresolved PR reviews exist, include an explicit unresolved-reviews list (reviewer + direct link per unresolved review thread) + - `resolve_review_threads`: an array of unresolved PR review thread node IDs to resolve via safe output; include a thread only when the thread already contains a follow-up response from the PR author or `@copilot` that addresses the feedback - `dismiss_reviews`: an array of review IDs — include a review ID only when the review was authored by `github-actions[bot]` with `CHANGES_REQUESTED` state AND all review threads on the PR are resolved (no unresolved threads remain); return an empty array if there are unresolved threads or no qualifying reviews 4. Make at most 8 tool calls total. If 8 calls are insufficient to reach a confident decision, set all fields to `null` and set `skip_reason: "insufficient_context"`. 5. Keep output compact JSON only — a single object, no prose. diff --git a/pkg/cli/pr_sous_chef_workflow_contract_test.go b/pkg/cli/pr_sous_chef_workflow_contract_test.go index f59b0d03053..03f981fdae6 100644 --- a/pkg/cli/pr_sous_chef_workflow_contract_test.go +++ b/pkg/cli/pr_sous_chef_workflow_contract_test.go @@ -27,7 +27,10 @@ func TestPRSousChefWorkflowAddCommentTargetContract(t *testing.T) { assert.Contains(t, text, "Never emit `add_comment` without a numeric target field", "Workflow must forbid targetless add_comment items") assert.Contains(t, text, "pr_number 12345", "Workflow should include a concrete add_comment pr_number example") assert.Contains(t, text, "include an explicit unresolved-reviews list", "Workflow should require explicit unresolved review listing in nudge comments") - assert.Contains(t, text, "Process all eligible PRs per run.", "Workflow should require processing all eligible PRs") + assert.Contains(t, text, "Process at most 4 nudges per run.", "Workflow should cap nudges per run") + assert.Contains(t, text, "add-comment:\n max: 4", "Workflow should hard-cap add_comment safe-output calls to 4 per run") + assert.Contains(t, text, "Prioritize which PRs to nudge, in this order:", "Workflow should define deterministic PR prioritization for nudges") + assert.Contains(t, text, "stop creating new nudge comments once 4 PRs have been nudged", "Workflow should enforce a hard per-run nudge limit") assert.Contains(t, text, "Make at most 8 tool calls total.", "Sub-agent should have a hard tool-call budget") assert.Contains(t, text, "model: sonnet", "Sub-agent should use a Sonnet model alias") assert.Contains(t, text, "skip_reason: \"sub_agent_error\"", "Workflow should skip failed sub-agent responses without retry") @@ -35,6 +38,10 @@ func TestPRSousChefWorkflowAddCommentTargetContract(t *testing.T) { assert.Contains(t, text, ".prs | length", "eligible_count should reflect the number of eligible PRs") assert.Contains(t, text, "dismiss-pull-request-review", "Workflow should configure the native safe-output path for dismissing stale github-actions reviews") assert.Contains(t, text, "safeoutputs dismiss_pull_request_review", "Workflow should call the native dismiss_pull_request_review safe-output tool") + assert.Contains(t, text, "resolve-pull-request-review-thread", "Workflow should configure native safe-output support for resolving review threads") + assert.Contains(t, text, "safeoutputs resolve_pull_request_review_thread", "Workflow should call the native resolve_pull_request_review_thread safe-output tool") + assert.Contains(t, text, "resolve_review_threads", "Workflow should track and process resolvable review threads") + assert.Contains(t, text, "skip_reason: \"resolve_review_thread_failed\"", "Workflow should explicitly record failed review-thread resolution attempts") assert.Contains(t, text, "all review threads on the PR are resolved", "Workflow must dismiss reviews only when all PR review threads are resolved") assert.Contains(t, text, "slash-command runs are acknowledgment nudges and must not perform automated review cleanup", "Workflow must require slash-command runs to skip dismissal") assert.Contains(t, text, "dismissed_reviews", "Noop summary must include dismissed_reviews counter")