diff --git a/.github/workflows/test-gap-finder.lock.yml b/.github/workflows/test-gap-finder.lock.yml index 2ec31fb6..73de4b91 100644 --- a/.github/workflows/test-gap-finder.lock.yml +++ b/.github/workflows/test-gap-finder.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"2bbb20c5f6f94b20874d8b6efc8f1be6b7e8ef37bd931767d1d1071b36270753","compiler_version":"v0.74.4","strict":true,"agent_id":"copilot"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/github-script","sha":"d746ffe35508b1917358783b479e04febd2b8f71","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"d3abfe96a194bce3a523ed2093ddedd5704cdf62","version":"v0.74.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"f4d2ebc26cf5739f4ba20a33e7b04a091f9257a2c1be7cdf915cc78ceb2b5d94","compiler_version":"v0.74.4","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_CI_TRIGGER_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/cache/restore","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/cache/save","sha":"27d5ce7f107fe9357f9df03efb73ab90386fccae","version":"v5.0.5"},{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/github-script","sha":"d746ffe35508b1917358783b479e04febd2b8f71","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"d3abfe96a194bce3a523ed2093ddedd5704cdf62","version":"v0.74.4"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.46"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.46"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.9","digest":"sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.9@sha256:64828b42a4482f58fab16509d7f8f495a6d97c972a98a68aff20543531ac0388"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4"},{"image":"node:lts-alpine","digest":"sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f","pinned_image":"node:lts-alpine@sha256:d1b3b4da11eefd5941e7f0b9cf17783fc99d9c6fc34884a665f40a06dbdfc94f"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -22,10 +22,11 @@ # # For more information: https://github.github.com/gh-aw/introduction/overview/ # -# Analyzes test coverage and suggests missing test cases for untested compiler paths +# Analyzes test coverage and contributes missing test cases through focused pull requests # # Secrets used: # - COPILOT_GITHUB_TOKEN +# - GH_AW_CI_TRIGGER_TOKEN # - GH_AW_GITHUB_MCP_SERVER_TOKEN # - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN @@ -189,21 +190,24 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_f01542e0121381da_EOF' + cat << 'GH_AW_PROMPT_7fdaae13bdf6cd05_EOF' - GH_AW_PROMPT_f01542e0121381da_EOF + GH_AW_PROMPT_7fdaae13bdf6cd05_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/cache_memory_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_f01542e0121381da_EOF' + cat << 'GH_AW_PROMPT_7fdaae13bdf6cd05_EOF' - Tools: create_issue, missing_tool, missing_data, noop + Tools: create_pull_request, missing_tool, missing_data, noop + GH_AW_PROMPT_7fdaae13bdf6cd05_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_create_pull_request.md" + cat << 'GH_AW_PROMPT_7fdaae13bdf6cd05_EOF' - GH_AW_PROMPT_f01542e0121381da_EOF + GH_AW_PROMPT_7fdaae13bdf6cd05_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_f01542e0121381da_EOF' + cat << 'GH_AW_PROMPT_7fdaae13bdf6cd05_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -232,12 +236,12 @@ jobs: {{/if}} - GH_AW_PROMPT_f01542e0121381da_EOF + GH_AW_PROMPT_7fdaae13bdf6cd05_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - cat << 'GH_AW_PROMPT_f01542e0121381da_EOF' + cat << 'GH_AW_PROMPT_7fdaae13bdf6cd05_EOF' {{#runtime-import .github/workflows/test-gap-finder.md}} - GH_AW_PROMPT_f01542e0121381da_EOF + GH_AW_PROMPT_7fdaae13bdf6cd05_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 @@ -462,32 +466,43 @@ 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_f073ed8cac788bb9_EOF' - {"create_issue":{"max":1},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} - GH_AW_SAFE_OUTPUTS_CONFIG_f073ed8cac788bb9_EOF + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0e840311fc39a686_EOF' + {"create_pull_request":{"allowed_files":["tests/**"],"max":1,"max_patch_files":100,"max_patch_size":1024,"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","AGENTS.md","CLAUDE.md","GEMINI.md"]},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_0e840311fc39a686_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { "description_suffixes": { - "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created." + "create_pull_request": " CONSTRAINTS: Maximum 1 pull request(s) can be created." }, "repo_params": {}, "dynamic_tools": [] } GH_AW_VALIDATION_JSON: | { - "create_issue": { + "create_pull_request": { "defaultMax": 1, "fields": { + "base": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, "body": { "required": true, "type": "string", "sanitize": true, "maxLength": 65000 }, - "fields": { - "type": "array" + "branch": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "draft": { + "type": "boolean" }, "labels": { "type": "array", @@ -495,16 +510,10 @@ jobs: "itemSanitize": true, "itemMaxLength": 128 }, - "parent": { - "issueOrPRNumber": true - }, "repo": { "type": "string", "maxLength": 256 }, - "temporary_id": { - "type": "string" - }, "title": { "required": true, "type": "string", @@ -672,7 +681,7 @@ jobs: mkdir -p /home/runner/.copilot GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_d04194c12e1e2988_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_615fe1ea326c2573_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { @@ -713,7 +722,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_d04194c12e1e2988_EOF + GH_AW_MCP_CONFIG_615fe1ea326c2573_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -962,8 +971,9 @@ jobs: needs.activation.outputs.stale_lock_file_failed == 'true') runs-on: ubuntu-slim permissions: - contents: read + contents: write issues: write + pull-requests: write concurrency: group: "gh-aw-conclusion-test-gap-finder" cancel-in-progress: false @@ -1083,6 +1093,8 @@ jobs: GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_CODE_PUSH_FAILURE_ERRORS: ${{ needs.safe_outputs.outputs.code_push_failure_errors }} + GH_AW_CODE_PUSH_FAILURE_COUNT: ${{ needs.safe_outputs.outputs.code_push_failure_count }} GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} GH_AW_GROUP_REPORTS: "false" @@ -1192,7 +1204,7 @@ jobs: uses: actions/github-script@d746ffe35508b1917358783b479e04febd2b8f71 # v9.0.0 env: WORKFLOW_NAME: "Test Gap Finder" - WORKFLOW_DESCRIPTION: "Analyzes test coverage and suggests missing test cases for untested compiler paths" + WORKFLOW_DESCRIPTION: "Analyzes test coverage and contributes missing test cases through focused pull requests" HAS_PATCH: ${{ needs.agent.outputs.has_patch }} with: script: | @@ -1307,8 +1319,9 @@ jobs: if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim permissions: - contents: read + contents: write issues: write + pull-requests: write timeout-minutes: 15 env: GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/test-gap-finder" @@ -1325,8 +1338,8 @@ jobs: code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} - created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + created_pr_number: ${{ steps.process_safe_outputs.outputs.created_pr_number }} + created_pr_url: ${{ steps.process_safe_outputs.outputs.created_pr_url }} process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} steps: @@ -1357,6 +1370,57 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Download patch artifact + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Extract base branch from agent output + id: extract-base-branch + if: steps.download-agent-output.outcome == 'success' + shell: bash + run: | + if [ -f "/tmp/gh-aw/agent_output.json" ]; then + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + BASE_BRANCH=$("$GH_AW_NODE" -e " + try { + const data = JSON.parse(require('fs').readFileSync('/tmp/gh-aw/agent_output.json', 'utf8')); + const item = (data.items || []).find(i => + (i.type === 'create_pull_request' || i.type === 'push_to_pull_request_branch') && + i.base_branch + ); + if (item) process.stdout.write(item.base_branch); + } catch(e) {} + " 2>/dev/null || true) + # Validate: only allow safe git branch name characters + if [[ "$BASE_BRANCH" =~ ^[a-zA-Z0-9/_.-]+$ ]] && [ ${#BASE_BRANCH} -le 255 ]; then + printf 'base-branch=%s\n' "$BASE_BRANCH" >> "$GITHUB_OUTPUT" + echo "Extracted base branch from safe output: $BASE_BRANCH" + fi + fi + - name: Checkout repository + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + ref: ${{ steps.extract-base-branch.outputs.base-branch || github.base_ref || github.event.pull_request.base.ref || github.ref_name || github.event.repository.default_branch }} + token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + persist-credentials: false + fetch-depth: 1 + - name: Configure Git credentials + if: (!cancelled()) && needs.agent.result != 'skipped' && contains(needs.agent.outputs.output_types, 'create_pull_request') + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GIT_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GIT_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash @@ -1374,7 +1438,8 @@ jobs: GH_AW_ALLOWED_DOMAINS: "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,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,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,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,sh.rustup.rs,static.crates.io,static.rust-lang.org,telemetry.enterprise.githubcopilot.com,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: "{\"create_issue\":{\"max\":1},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request\":{\"allowed_files\":[\"tests/**\"],\"max\":1,\"max_patch_files\":100,\"max_patch_size\":1024,\"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\",\"AGENTS.md\",\"CLAUDE.md\",\"GEMINI.md\"]},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + GH_AW_CI_TRIGGER_TOKEN: ${{ secrets.GH_AW_CI_TRIGGER_TOKEN }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/test-gap-finder.md b/.github/workflows/test-gap-finder.md index 837cd802..9f4d4991 100644 --- a/.github/workflows/test-gap-finder.md +++ b/.github/workflows/test-gap-finder.md @@ -1,7 +1,7 @@ --- on: schedule: daily on weekdays -description: Analyzes test coverage and suggests missing test cases for untested compiler paths +description: Analyzes test coverage and contributes missing test cases through focused pull requests permissions: contents: read issues: read @@ -13,8 +13,10 @@ tools: network: allowed: [defaults, rust] safe-outputs: - create-issue: + create-pull-request: max: 1 + allowed-files: + - "tests/**" --- # Test Gap Finder @@ -99,55 +101,56 @@ cat > /tmp/gh-aw/cache-memory/test-gap-state.json << 'EOF' EOF ``` -## Step 5: Create Issue (If Warranted) +## Step 5: Implement Missing Tests and Open a PR -**Create an issue** if you find 3+ meaningful test gaps, or any gap in security-critical code (`sanitize.rs`, `proxy.rs`, `mcp_firewall.rs`). +If you find meaningful gaps, implement them directly in `tests/**` instead of filing an issue. +For this workflow, **high-value** means coverage for security-sensitive paths, error-handling branches, or previously untested public behavior. -**Do NOT create an issue** if: +Scope limits per run: +- Add or update at most **3** high-value tests. +- Keep the changes focused on one module/area. +- Skip speculative or flaky tests. + +Before opening a PR, run: + +```bash +cargo test 2>&1 +cargo clippy --all-targets --all-features 2>&1 +``` + +Open at most one pull request via `create-pull-request` when tests were added and checks passed. +Note: this repository requires `GH_AW_CI_TRIGGER_TOKEN` for PR CI triggers when using `create-pull-request`. +PRs opened via the default `GITHUB_TOKEN` do not trigger follow-up workflows. +Set `GH_AW_CI_TRIGGER_TOKEN` in **Repository Settings โ†’ Secrets and variables โ†’ Actions** with token permissions that allow PR creation and workflow triggering (for example `contents: write` and `workflows: write`). + +**Do NOT open a PR** if: - All significant paths are covered - Only trivial gaps remain -- The same gaps were already reported (check cache-memory and recent open issues) +- You cannot get the test suite back to passing -Before creating an issue, search for existing open issues to avoid duplicates: -- Search for issues with "test gap" or "test coverage" in the title +## PR Format -## Issue Format - -**Title**: `๐Ÿงช Test gap analysis โ€” [N] gaps found in [area]` +**Title**: `test: add coverage for [module/area]` **Body**: ```markdown -## Test Gap Analysis +## Test Gap Fixes **Test suite snapshot**: [X] unit tests, [Y] integration tests, [Z] test fixtures -### Priority Gaps - -| Module | Function/Path | Why It Matters | Suggested Test | -|--------|--------------|----------------|----------------| -| `sanitize.rs` | `sanitize_yaml_value` with nested expressions | Security-critical input sanitization | Test with template expressions embedded in agent name | +### Added Coverage -### Suggested Test Cases +| Module | Function/Path | Why It Matters | Test Added | +|--------|--------------|----------------|------------| +| `sanitize.rs` | `sanitize_yaml_value` with nested expressions | Security-critical input sanitization | `test_sanitize_yaml_value_nested_expression` | -#### 1. [Test name] -```rust -#[test] -fn test_description() { - // Setup - // Action - // Assert -} -``` +### Validation -#### 2. [Test name] -... - -### Coverage Summary - -| Module | Public Fns | Tests | Coverage Estimate | -|--------|-----------|-------|-------------------| -| `compile/standalone.rs` | N | M | ~X% | +- [x] `cargo test` +- [x] `cargo clippy --all-targets --all-features` --- -*This issue was created by the automated test gap finder. Previous run: [date]. Modules audited this cycle: [list].* +*This PR was created by the automated test gap finder. Previous run: [date]. Modules audited this cycle: [list].* ``` + +If no meaningful, safe test additions are found, call the `noop` safe-output tool with a brief explanation.