diff --git a/.github/scripts/tests/agent-ingress-workflow.bats b/.github/scripts/tests/agent-ingress-workflow.bats new file mode 100644 index 00000000..6e32d78d --- /dev/null +++ b/.github/scripts/tests/agent-ingress-workflow.bats @@ -0,0 +1,163 @@ +#!/usr/bin/env bats +# Tests for .github/workflows/agent-ingress.yml — static YAML assertions, no live +# API calls. +# +# Regression guard for the ADR-0007 pilot collapse (issue #506): the 5 Class-1 +# event-driven per-role caller stubs are replaced by ONE ingress with one thin +# caller job per role. Source of truth is the collapse package in +# petry-projects/.github-private docs/initiatives/agent-ingress-collapse-markets.md +# (§3 content, §2 eligible set + carve-outs, §4/ADR-0010 job-level concurrency). + +REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)" +WORKFLOWS="$REPO_ROOT/.github/workflows" +WORKFLOW="$WORKFLOWS/agent-ingress.yml" + +ROLES=(dev-lead pr-auto-review pr-review pr-review-mention ci-failure-analyst) + +@test "ingress workflow exists and is valid YAML" { + [ -f "$WORKFLOW" ] + yq '.' "$WORKFLOW" >/dev/null +} + +@test "top-level permissions are empty" { + run yq -o=json -I=0 '.permissions' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "{}" ] +} + +@test "ingress carries exactly one job per collapsed role" { + run yq '.jobs | keys | sort | join(",")' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "ci-failure-analyst,dev-lead,pr-auto-review,pr-review,pr-review-mention" ] +} + +@test "every job is a thin caller with a reusable pin and an event-filter if:" { + for role in "${ROLES[@]}"; do + run yq ".jobs.\"$role\".uses" "$WORKFLOW" + [ "$status" -eq 0 ] + uses="$output" + run yq ".jobs.\"$role\".if" "$WORKFLOW" + [ "$status" -eq 0 ] + cond="$output" + [[ "$uses" == petry-projects/*/.github/workflows/*.yml@* ]] || { printf '%s\n' "$role: bad uses: $uses"; return 1; } + [[ "$cond" == *"github.event_name"* ]] || { printf '%s\n' "$role: if: missing event filter"; return 1; } + run yq ".jobs.\"$role\" | has(\"steps\")" "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "false" ] || { printf '%s\n' "$role: has steps"; return 1; } + done +} + +@test "legacy per-role stubs are removed" { + for f in dev-lead pr-auto-review pr-review pr-review-mention ci-failure-analyst; do + [ ! -e "$WORKFLOWS/$f.yml" ] || { echo "stale stub: $f.yml"; return 1; } + done +} + +@test "carve-out and required-gate stubs are kept (package §2b/§2c)" { + for f in add-to-project dependabot-automerge dependabot-rebase auto-rebase \ + feature-ideation initiative-driver agent-shield dependency-audit; do + [ -f "$WORKFLOWS/$f.yml" ] || { echo "missing carve-out: $f.yml"; return 1; } + done +} + +@test "on: is the union of the collapsed roles' triggers" { + run yq '.on | keys | sort | join(",")' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "check_run,check_suite,issue_comment,issues,pull_request,pull_request_review,pull_request_review_comment,repository_dispatch,workflow_dispatch,workflow_run" ] + run yq '.on.pull_request.types | sort | join(",")' "$WORKFLOW" + [ "$output" = "opened,ready_for_review,reopened,review_requested,synchronize" ] + run yq '.on.workflow_run.workflows | join(",")' "$WORKFLOW" + [ "$output" = "CI" ] + run yq '.on.repository_dispatch.types | sort | join(",")' "$WORKFLOW" + [ "$output" = "dev-lead-ci-failure,dev-lead-issue-retry,dev-lead-reviews-retry,pr-review-mention" ] +} + +@test "on: does not carry pull_request_target, push, or schedule" { + for ev in pull_request_target push schedule; do + run yq ".on | has(\"$ev\")" "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "false" ] || { printf '%s\n' "unexpected trigger: $ev"; return 1; } + done +} + +@test "each role keeps its pre-collapse pin (behavior parity)" { + run yq '.jobs.dev-lead.uses' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"/dev-lead-reusable.yml@dev-lead/v139-stable" ]] + run yq '.jobs.dev-lead.with.agent_ref' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "dev-lead/v139-stable" ] + run yq '.jobs.pr-auto-review.uses' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"/pr-auto-review-reusable.yml@pr-auto-review/v1-stable" ]] + run yq '.jobs.pr-review.uses' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"/pr-review.yml@pr-review/stable" ]] + run yq '.jobs.pr-review.with.agent_ref' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "pr-review/stable" ] + run yq '.jobs.pr-review-mention.uses' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"/pr-review-mention-reusable.yml@pr-review-mention/v2-stable" ]] + run yq '.jobs.ci-failure-analyst.uses' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"/ci-failure-analyst-reusable.yml@b58510275dd1cbbd13c0733c15265d51fd63b992" ]] +} + +@test "dev-lead job keeps the base=main PR filter and declares no caller concurrency" { + run yq '.jobs.dev-lead.if' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"github.event.pull_request.base.ref == 'main'"* ]] + # dev-lead centralises concurrency inside its reusable (ADR-0010 collision case). + run yq '.jobs.dev-lead | has("concurrency")' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "false" ] +} + +@test "job-level concurrency groups are role-prefixed with literal cancel-in-progress (ADR-0010)" { + for role in pr-auto-review pr-review ci-failure-analyst; do + run yq ".jobs.\"$role\".concurrency.group" "$WORKFLOW" + [ "$status" -eq 0 ] + group="$output" + run yq ".jobs.\"$role\".concurrency.cancel-in-progress | tag" "$WORKFLOW" + [ "$status" -eq 0 ] + cancel="$output" + [[ "$group" == "$role-"* ]] || { printf '%s\n' "$role: group lacks role prefix: $group"; return 1; } + # ADR-0010 forbids run_id and the inputs context (github.event.inputs.* is payload). + payload_stripped="${group//github.event.inputs./}" + [[ "$group" != *"run_id"* && "$payload_stripped" != *"inputs."* ]] \ + || { printf '%s\n' "$role: group reads run_id/inputs context"; return 1; } + [ "$cancel" = "!!bool" ] || { printf '%s\n' "$role: cancel-in-progress not a literal bool"; return 1; } + done + run yq '.jobs.ci-failure-analyst.concurrency.cancel-in-progress' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "false" ] +} + +@test "pr-review concurrency fallback is 'enumerate', never the reusable's 'batch' group" { + run yq '.jobs.pr-review.concurrency.group' "$WORKFLOW" + [[ "$output" == *"'enumerate'"* ]] + [[ "$output" != *"'batch'"* ]] +} + +@test "pr-review job keeps the check_suite-with-no-PR skip" { + run yq '.jobs.pr-review.if' "$WORKFLOW" + [[ "$output" == *"github.event.check_suite.pull_requests[0] != null"* ]] +} + +@test "ci-failure-analyst job skips non-failures and its own check run" { + run yq '.jobs.ci-failure-analyst.if' "$WORKFLOW" + [[ "$output" == *"github.event_name == 'check_run'"* ]] + [[ "$output" == *"github.event.check_run.conclusion == 'failure'"* ]] + [[ "$output" == *"!startsWith(github.event.check_run.name, 'CI Failure Analyst')"* ]] +} + +@test "if: guards never read repo state (vars/secrets/needs/hashFiles)" { + for role in "${ROLES[@]}"; do + run yq ".jobs.\"$role\".if" "$WORKFLOW" + [ "$status" -eq 0 ] + cond="$output" + [[ "$cond" != *"vars."* && "$cond" != *"secrets."* && "$cond" != *"needs."* && "$cond" != *"hashFiles"* ]] \ + || { printf '%s\n' "$role: if: reaches repo state"; return 1; } + done +} diff --git a/.github/scripts/tests/pr-review-mention-workflow.bats b/.github/scripts/tests/pr-review-mention-workflow.bats index 6af9315d..83146ca7 100755 --- a/.github/scripts/tests/pr-review-mention-workflow.bats +++ b/.github/scripts/tests/pr-review-mention-workflow.bats @@ -1,14 +1,17 @@ #!/usr/bin/env bats -# Tests for .github/workflows/pr-review-mention.yml — static YAML assertions, -# no live API calls. +# Tests for the `pr-review-mention` job in .github/workflows/agent-ingress.yml — +# static YAML assertions, no live API calls. # # Regression guard for issue #307: Copilot-triggered `pull_request_review_comment` # events were terminating as `action_required` (approval-gated), inflating the # workflow's failure rate. The fix is a job-level `if:` guard that skips -# Bot-typed senders. These tests also pin the constraints the file's AGENTS -# header forbids changing (the `uses:` ref, triggers, and permissions block). +# Bot-typed senders. These tests also pin the constraints the stub's AGENTS +# header forbade changing (the `uses:` ref, triggers, and permissions block). +# +# The per-role pr-review-mention.yml stub was collapsed into agent-ingress.yml +# (ADR-0007, issue #506); the guards now apply to the ingress job. -WORKFLOW="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)/.github/workflows/pr-review-mention.yml" +WORKFLOW="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)/.github/workflows/agent-ingress.yml" @test "workflow file exists" { [ -f "$WORKFLOW" ] @@ -24,8 +27,7 @@ WORKFLOW="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)/.github/workf [ "$output" != "null" ] [ -n "$output" ] # The guard must reference the sender type so Copilot/bot events are skipped. - [[ "$output" == *"github.event.sender.type"* ]] - [[ "$output" == *"Bot"* ]] + [[ "$output" == *"github.event.sender.type != 'Bot'"* ]] } @test "uses: ref stays pinned to the pr-review-mention/v2-stable channel" { @@ -38,6 +40,30 @@ WORKFLOW="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../.." && pwd)/.github/workf run yq '[.on | has("issue_comment"), .on | has("pull_request_review_comment"), .on | has("pull_request")] | all' "$WORKFLOW" [ "$status" -eq 0 ] [ "$output" = "true" ] + run yq '.on.pull_request.types | contains(["review_requested"])' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "true" ] +} + +@test "pr-review-mention job if: selects exactly its original subscription" { + run yq '.jobs.pr-review-mention.if' "$WORKFLOW" + [ "$status" -eq 0 ] + [[ "$output" == *"github.event_name == 'pull_request' && github.event.action == 'review_requested'"* ]] + [[ "$output" == *"github.event_name == 'issue_comment'"* ]] + [[ "$output" == *"github.event_name == 'pull_request_review_comment'"* ]] + # Exactly three subscription branches, with the Bot guard AND-connected to the group. + [ "$(grep -o "github.event_name ==" <<<"$output" | wc -l)" -eq 3 ] + [[ "$output" == *") && github.event.sender.type != 'Bot'"* || "$output" == *"&& github.event.sender.type != 'Bot'" ]] + [[ "$output" != *"|| github.event.sender.type"* ]] +} + +@test "comment triggers are restricted to created" { + run yq '.on.issue_comment.types | join(",")' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "created" ] + run yq '.on.pull_request_review_comment.types | join(",")' "$WORKFLOW" + [ "$status" -eq 0 ] + [ "$output" = "created" ] } @test "job-level pull-requests: write permission is preserved" { diff --git a/.github/workflows/agent-ingress.yml b/.github/workflows/agent-ingress.yml new file mode 100644 index 00000000..5cdac1ad --- /dev/null +++ b/.github/workflows/agent-ingress.yml @@ -0,0 +1,184 @@ +# ───────────────────────────────────────────────────────────────────────────── +# Agent Ingress — petry-projects/markets (ADR-0007, epic #1723, story #1729) +# +# ONE ingress for this repo's eligible Class-1 event-driven agentic roles. The +# on: block is the UNION of the collapsed roles' triggers; each job below is ONE +# role, guarded by a PURE event-filter if: that reconstructs that role's original +# subscription. pull_request_target roles (add-to-project, dependabot-automerge, +# dependabot-rebase), repo-local (auto-rebase), Class-3 (feature-ideation, +# initiative-driver), and required-check gates (agent-shield, dependency-audit) +# keep their own stubs — see docs/initiatives/agent-ingress-collapse-markets.md. +# ───────────────────────────────────────────────────────────────────────────── + +name: Agent Ingress + +on: + pull_request: + # opened/reopened/synchronize ← dev-lead(base=main), pr-auto-review, pr-review; + # ready_for_review ← pr-auto-review, pr-review; review_requested ← pr-review-mention. + types: [opened, reopened, synchronize, ready_for_review, review_requested] + pull_request_review: + # submitted ← dev-lead, pr-auto-review, pr-review; dismissed ← pr-auto-review, pr-review. + types: [submitted, dismissed] + pull_request_review_comment: + types: [created] + issue_comment: + types: [created] + issues: + types: [labeled] + check_run: + types: [completed] + check_suite: + types: [completed] + workflow_run: + workflows: ["CI"] + types: [completed] + workflow_dispatch: + inputs: + pr_url: + description: "Optional: review a single PR URL instead of enumerating" + required: false + type: string + dry_run: + description: "If true, never submit reviews or comments" + required: false + default: "false" + type: string + force_review: + description: "If true, bypass idempotency and re-review at the same head SHA" + required: false + default: "false" + type: string + repository_dispatch: + types: [dev-lead-ci-failure, dev-lead-reviews-retry, dev-lead-issue-retry, pr-review-mention] + +permissions: {} + +jobs: + # ── dev-lead ──────────────────────────────────────────────────────────────── + dev-lead: + if: >- + (github.event_name == 'pull_request' + && contains(fromJSON('["opened","reopened","synchronize"]'), github.event.action) + && github.event.pull_request.base.ref == 'main') + || (github.event_name == 'pull_request_review' && github.event.action == 'submitted') + || github.event_name == 'pull_request_review_comment' + || github.event_name == 'issue_comment' + || github.event_name == 'issues' + || github.event_name == 'check_run' + || (github.event_name == 'repository_dispatch' + && contains(fromJSON('["dev-lead-ci-failure","dev-lead-reviews-retry","dev-lead-issue-retry"]'), github.event.action)) + uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v139-stable # NOSONAR(githubactions:S7637) first-party channel ref + with: + agent_ref: dev-lead/v139-stable + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable + permissions: + contents: write + pull-requests: write + issues: write + actions: read + checks: read + statuses: read + + # ── pr-auto-review ────────────────────────────────────────────────────────── + pr-auto-review: + if: >- + (github.event_name == 'pull_request' + && contains(fromJSON('["opened","reopened","synchronize","ready_for_review"]'), github.event.action)) + || (github.event_name == 'pull_request_review' + && contains(fromJSON('["submitted","dismissed"]'), github.event.action)) + || github.event_name == 'check_suite' + || github.event_name == 'workflow_run' + # Workflow-level concurrency in the source stub → job-level here, bounded per + # ADR-0010: literal role prefix, payload parts only (no run_id), literal bool. + # PR events keep their per-PR slot; check_suite / workflow_run use their own + # event ids so runs on a shared commit never cancel another PR's review (§4b). + concurrency: + group: >- + pr-auto-review-${{ + github.event.pull_request.number + || github.event.check_suite.id + || github.event.workflow_run.id + || 'none' + }} + cancel-in-progress: true + permissions: + pull-requests: read + checks: read + actions: read + uses: petry-projects/.github/.github/workflows/pr-auto-review-reusable.yml@pr-auto-review/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref + secrets: + GH_PAT_WORKFLOWS: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} + + # ── pr-review ───────────────────────────────────────────────────────────────── + pr-review: + # Subscription filter AND the source stub's check_suite-with-no-PR skip, both + # pure event predicates over the delivered payload. + if: >- + ( + (github.event_name == 'pull_request' + && contains(fromJSON('["ready_for_review","reopened","synchronize"]'), github.event.action)) + || (github.event_name == 'pull_request_review' + && contains(fromJSON('["submitted","dismissed"]'), github.event.action)) + || github.event_name == 'check_suite' + || github.event_name == 'workflow_dispatch' + || (github.event_name == 'repository_dispatch' && github.event.action == 'pr-review-mention') + ) + && (github.event_name != 'check_suite' || github.event.check_suite.pull_requests[0] != null) + # Job-level concurrency, bounded per ADR-0010: payload parts only (no inputs.*, + # no run_id). A sweep with no PR shares the 'enumerate' slot. The fallback is + # NOT 'batch', because the pinned reusable declares pr-review-batch (§4b). + concurrency: + group: >- + pr-review-${{ + (github.event.pull_request && github.event.pull_request.number) + || (github.event.check_suite && github.event.check_suite.pull_requests[0] && github.event.check_suite.pull_requests[0].number) + || github.event.inputs.pr_url + || (github.event.client_payload && github.event.client_payload.pr_url) + || 'enumerate' + }} + cancel-in-progress: true + permissions: + contents: read + pull-requests: write + checks: read + uses: petry-projects/.github-private/.github/workflows/pr-review.yml@pr-review/stable # NOSONAR(githubactions:S7637) first-party channel ref # DEFECTIVE PIN — see §5 + with: + agent_ref: pr-review/stable + pr_url: ${{ inputs.pr_url || '' }} + dry_run: ${{ inputs.dry_run || '' }} + force_review: ${{ inputs.force_review || '' }} + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable + + # ── pr-review-mention ────────────────────────────────────────────────────────── + pr-review-mention: + if: >- + ( + (github.event_name == 'pull_request' && github.event.action == 'review_requested') + || github.event_name == 'issue_comment' + || github.event_name == 'pull_request_review_comment' + ) + && github.event.sender.type != 'Bot' + permissions: + pull-requests: write + uses: petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@pr-review-mention/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref + secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable + + # ── ci-failure-analyst ───────────────────────────────────────────────────────── + ci-failure-analyst: + if: >- + github.event_name == 'check_run' + && github.event.check_run.conclusion == 'failure' + && !startsWith(github.event.check_run.name, 'CI Failure Analyst') + concurrency: + group: "ci-failure-analyst-${{ github.event.check_run.head_sha }}" + cancel-in-progress: false + permissions: + pull-requests: write + issues: write + actions: read + contents: read + checks: read + uses: petry-projects/.github-private/.github/workflows/ci-failure-analyst-reusable.yml@b58510275dd1cbbd13c0733c15265d51fd63b992 # DEFECTIVE PIN — bare SHA, see §5 + secrets: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/ci-failure-analyst.yml b/.github/workflows/ci-failure-analyst.yml deleted file mode 100644 index 2a5610bb..00000000 --- a/.github/workflows/ci-failure-analyst.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Deploy this file to target repos as: .github/workflows/ci-failure-analyst.yml -# -# Purpose: Posts a read-only diagnostic comment on PRs within 2 minutes of a -# CI check failure. Identifies the failing step, classifies the root cause, -# and suggests a concrete fix action. -# -# All analysis logic lives in petry-projects/.github-private — this stub is -# intentionally minimal and never needs modification after initial deployment. -# -# Requirements: -# CLAUDE_CODE_OAUTH_TOKEN org secret — must be granted to this repo in org -# secret settings (Settings -> Secrets -> Actions) -# -# This workflow is independent of dev-lead. Both can coexist in the same repo: -# dev-lead — diagnoses and attempts to fix CI failures (writes commits) -# ci-failure-analyst — diagnoses only, always posts a fast diagnostic comment -# -# Docs: https://github.com/petry-projects/.github-private/blob/main/docs/aw/ci-failure-analyst.md -# ───────────────────────────────────────────────────────────────────────────── - -name: "CI Failure Analyst" - -on: - check_run: - types: [completed] - -permissions: - pull-requests: write - issues: write # post/read PR comments via the Issues comments API - actions: read - contents: read - checks: read - -concurrency: - group: "ci-failure-analyst-${{ github.event.check_run.head_sha }}" - cancel-in-progress: false - -jobs: - analyze: - # Skip non-failures and the analyst's own check run to prevent loops. - if: >- - github.event.check_run.conclusion == 'failure' && - !startsWith(github.event.check_run.name, 'CI Failure Analyst') - uses: petry-projects/.github-private/.github/workflows/ci-failure-analyst-reusable.yml@b58510275dd1cbbd13c0733c15265d51fd63b992 # main - secrets: - CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} diff --git a/.github/workflows/dev-lead.yml b/.github/workflows/dev-lead.yml deleted file mode 100644 index 1862c9b7..00000000 --- a/.github/workflows/dev-lead.yml +++ /dev/null @@ -1,74 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# Dev-Lead Agent — thin caller stub -# Standard: petry-projects/.github/standards/ci-standards.md#5-dev-lead-agent -# Reusable: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml -# -# ADOPTING THIS WORKFLOW: -# 1. Copy this file verbatim to .github/workflows/dev-lead.yml in your repo. -# 2. Ensure CLAUDE_CODE_OAUTH_TOKEN is set as an org or repo secret. -# 3. Optionally set GH_PAT_WORKFLOWS (required if Claude pushes workflow files). -# 4. Optionally set vars.DEV_LEAD_ENGINE = "claude" | "gemini" | "copilot". -# 5. SonarCloud-gated repo? Nothing to do — the `uses:` line below already -# carries the inline `# NOSONAR(githubactions:S7637)` marker. The ref is a -# first-party reusable pinned to a moving channel/ring tag, so without the -# marker SonarCloud's githubactions:S7637 would fail the Quality Gate. The -# marker travels with this file on copy; no sonar-project.properties entry -# is needed. See https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md#sonarcloud-exemption-first-party-reusable-ref-s7637. -# -# This stub is copied VERBATIM and is full-file identical across every adopting -# repo, modulo the per-repo ring/channel pin on the `uses:` ref and its matching -# `agent_ref` (below). Any other diff is drift, not a repo-specific liberty: -# `on:`, `permissions:`, and `concurrency:` are NOT repo-adjustable — do not trim -# a trigger, narrow a permission, or add a concurrency block on a PR branch. The -# behavior lives in the reusable, so change it there (or via a standards PR) and -# let the channel tag promote it centrally — never by editing this caller. See -# https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md#centralization-tiers and -# https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md#reusable-workflow-versioning--the-stable-channel. -# -# REQUIRED secrets: CLAUDE_CODE_OAUTH_TOKEN -# OPTIONAL secrets: GH_PAT_WORKFLOWS, GOOGLE_API_KEY, GH_PAT -# ───────────────────────────────────────────────────────────────────────────── - -name: Dev-Lead Agent - -on: - pull_request: - branches: [main] - types: [opened, reopened, synchronize] - pull_request_review: - types: [submitted] - pull_request_review_comment: - types: [created] - issue_comment: - types: [created] - issues: - types: [labeled] - check_run: - types: [completed] - repository_dispatch: - types: [dev-lead-ci-failure, dev-lead-reviews-retry, dev-lead-issue-retry] - -permissions: {} - -# Concurrency is centralised in the reusable workflow (dev-lead-reusable.yml) with -# per-issue / per-PR lanes, so issue pickups are never cancelled by PR follow-up -# traffic and the grouping can't drift per-repo. See petry-projects/.github#402. - -jobs: - dev-lead: - # Pinned to the moving dev-lead/v139-stable channel tag, not @main, so a broken - # change to dev-lead can no longer gate its own fix (the self-host circular - # dependency). Promotion is done by moving the dev-lead/v139-stable tag centrally; this - # caller is never edited on release. agent_ref threads the same channel into - # dev-lead's own scripts/prompts checkout. See https://github.com/petry-projects/.github/blob/main/standards/ci-standards.md#dev-lead-agent. - uses: petry-projects/.github-private/.github/workflows/dev-lead-reusable.yml@dev-lead/v139-stable # NOSONAR(githubactions:S7637) first-party channel ref - with: - agent_ref: dev-lead/v139-stable - secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable - permissions: - contents: write - pull-requests: write - issues: write - actions: read - checks: read - statuses: read diff --git a/.github/workflows/pr-auto-review.yml b/.github/workflows/pr-auto-review.yml deleted file mode 100644 index 7dc5a780..00000000 --- a/.github/workflows/pr-auto-review.yml +++ /dev/null @@ -1,77 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# SOURCE OF TRUTH: petry-projects/.github/standards/workflows/pr-auto-review.yml -# Standard: petry-projects/.github/standards/ci-standards.md -# Reusable: petry-projects/.github/.github/workflows/pr-auto-review-reusable.yml -# -# AGENTS — READ BEFORE EDITING: -# • This file is a THIN CALLER STUB. All readiness-gate logic lives in the -# reusable workflow above. -# • You MAY change: the workflow name(s) in `workflow_run.workflows` to match -# your repository's CI workflow name(s). -# • You MUST NOT change: the `@pr-auto-review/v1-stable` channel in the `uses:` -# line — this reusable is on the org-wide moving-channel model (rollout and -# rollback are a single central tag move; a frozen `@vN`/`@` pin is -# rejected by the compliance audit). Nor the trigger event types or the -# job-level `permissions:` block — reusable workflows can be granted no more -# permissions than the calling job, so removing the stanza breaks the -# reusable's gh API calls. -# • If you need different behaviour, open a PR against the reusable in the -# central repo. The change will propagate everywhere on next run. -# ───────────────────────────────────────────────────────────────────────────── -# -# PR Auto-Review — thin caller for the org-level reusable. -# To adopt: copy this file to .github/workflows/pr-auto-review.yml in your repo. -# Requires: GH_PAT_WORKFLOWS org secret (already present in petry-projects org). -name: PR Auto-Review — Ready Check - -on: - # workflow_run fires when a named GitHub Actions workflow completes. - # check_suite does NOT trigger for GitHub Actions runs, so this is required - # to catch CI turning green on a PR. - # This repo's CI workflow is named "CI" (.github/workflows/ci.yml). - workflow_run: - workflows: ["CI"] - types: [completed] - # check_suite covers third-party CI checks (e.g. SonarCloud, external apps). - check_suite: - types: [completed] - # Re-evaluate readiness after review state changes. - pull_request_review: - types: [submitted, dismissed] - # Re-evaluate when the PR is first opened, updated, or comes out of draft. - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - -permissions: {} - -# Deduplicate ONLY pull_request events which have a direct PR context (issue #1126). -# On GitHub Free (20 concurrent jobs, org-wide), avoid race conditions when multiple -# PRs share a commit: -# • pull_request / pull_request_review — have github.event.pull_request.number; -# safe to collapse per PR and cancel in progress. -# • check_suite / workflow_run — only have pull_requests[] array; if multiple PRs -# share the commit, pull_requests[0] is indeterminate and causes the first PR's -# events to cancel the second PR's events. Use unique per-run groups instead. -# A trigger with no associated PR (fork / no PR) also falls back to unique-per-run -# so unrelated workflow runs never interfere. -concurrency: - group: >- - ${{ - (github.event_name == 'pull_request' && format('pr-auto-review-ready-check-pr-{0}', github.event.pull_request.number)) - || (github.event_name == 'pull_request_review' && format('pr-auto-review-ready-check-pr-{0}', github.event.pull_request.number)) - || format('pr-auto-review-ready-check-unique-{0}', github.run_id) - }} - cancel-in-progress: ${{ github.event_name == 'pull_request' || github.event_name == 'pull_request_review' }} - -jobs: - pr-auto-review: - permissions: - pull-requests: read - checks: read - actions: read - uses: petry-projects/.github/.github/workflows/pr-auto-review-reusable.yml@pr-auto-review/v1-stable # NOSONAR(githubactions:S7637) first-party channel ref - secrets: - # Canonical-first fallback (.github-private#1326). The reusable resolves this - # secret BY NAME, so only the VALUE changes — the passed key stays named - # GH_PAT_WORKFLOWS. - GH_PAT_WORKFLOWS: ${{ secrets.GH_PAT_DON_PETRY || secrets.GH_PAT_WORKFLOWS }} diff --git a/.github/workflows/pr-review-mention.yml b/.github/workflows/pr-review-mention.yml deleted file mode 100644 index 52b6da49..00000000 --- a/.github/workflows/pr-review-mention.yml +++ /dev/null @@ -1,53 +0,0 @@ -# ───────────────────────────────────────────────────────────────────────────── -# SOURCE OF TRUTH: petry-projects/.github/standards/workflows/pr-review-mention.yml -# Standard: petry-projects/.github/standards/ci-standards.md -# Reusable: petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml -# -# AGENTS — READ BEFORE EDITING: -# • This file is a THIN CALLER STUB. All review-dispatch logic lives in the -# reusable workflow above. -# • You MUST NOT change: the `uses:` ref — it is pinned to the -# `pr-review-mention/v2-stable` channel, a moving tag advanced centrally. -# Never repoint it to `@main`, a SHA, or a frozen `@vX` (see -# ci-standards.md → Reusable workflow versioning). Also do not change the -# trigger events or the job-level `permissions:` block — reusable workflows -# can be granted no more permissions than the calling job, so removing the -# stanza breaks the reusable's gh API calls. -# • If you need different behaviour, open a PR against the reusable in the -# central repo. A new release is rolled out by moving the channel tag -# centrally — never by editing callers, so no fanout PR is needed. -# ───────────────────────────────────────────────────────────────────────────── -# -# PR Review Mention — thin caller for the org-level reusable. -# To adopt: copy this file to .github/workflows/pr-review-mention.yml in your repo. -# Requires: GH_PAT_WORKFLOWS org secret (already present in petry-projects org). -name: PR Review — Mention Trigger - -on: - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - pull_request: - types: [review_requested] - -permissions: {} - -jobs: - pr-review-mention: - # Skip events whose sender is a Bot (e.g. the Copilot coding agent), marking - # the job skipped (= success) and preventing an unnecessary reusable workflow - # invocation. The mention / review-request flow is human-driven, so a bot - # sender never carries a legitimate review request. - # Limitation: this job-level guard cannot prevent a workflow-level - # `action_required` state — GitHub enforces that approval gate before any - # `if:` condition is evaluated (see issue #307 for context). For bots that - # trigger the repo's first-time-contributor approval requirement the proper - # mitigation is to allowlist the bot account in Settings → Actions → - # "Fork pull request workflows from outside collaborators". - # `github.event.sender` is populated on all three trigger events. - if: github.event.sender.type != 'Bot' - permissions: - pull-requests: write - uses: petry-projects/.github/.github/workflows/pr-review-mention-reusable.yml@pr-review-mention/v2-stable # NOSONAR(githubactions:S7637) first-party channel ref - secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable diff --git a/.github/workflows/pr-review.yml b/.github/workflows/pr-review.yml deleted file mode 100644 index 3af85951..00000000 --- a/.github/workflows/pr-review.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: PR Review Agent - -# Thin caller for the org PR Review agent (petry-projects/.github-private), -# pinned to the known-good `pr-review/stable` channel. Adopted from the validated -# `.github-private/pr-review-trigger.yml` template (#536 consumer fan-out). -# -# - Version selection is the moving `pr-review/stable` tag — this file never -# changes on promotion; a release is rolled out by moving the tag centrally. -# - `agent_ref: pr-review/stable` pins the agent's own scripts to the same -# channel (#506), so the review logic AND scripts run the known-good version. -# - `secrets: inherit` forwards the org secrets, including -# `DON_PETRY_BOT_GH_PAT_CLASSIC` (classic PAT — required for approvals, since -# fine-grained PATs cannot `addPullRequestReview`). - -on: - check_suite: - types: [completed] - pull_request_review: - types: [submitted, dismissed] - pull_request: - types: [ready_for_review, reopened, synchronize] - workflow_dispatch: - inputs: - pr_url: - description: "Optional: review a single PR URL instead of enumerating" - required: false - type: string - dry_run: - description: "If true, never submit reviews or comments" - required: false - default: "false" - type: string - force_review: - description: "If true, bypass idempotency and re-review at the same head SHA" - required: false - default: "false" - type: string - repository_dispatch: - types: [pr-review-mention] - -permissions: - contents: read - pull-requests: write - checks: read - -# Collapse redundant overlapping runs for the same PR. The high-frequency -# triggers (synchronize + the resulting check_suite completions) fire several -# times for one PR head; without this they spawn concurrent duplicate reviews -# (the source of the cancelled runs). Group per PR when one is identifiable; -# fall back to a unique run id for batch/dispatch/no-PR runs so those are never -# cancelled against each other. -concurrency: - group: >- - pr-review-${{ - github.event.pull_request.number - || github.event.check_suite.pull_requests[0].number - || inputs.pr_url - || github.event.client_payload.pr_url - || github.run_id - }} - cancel-in-progress: true - -jobs: - review: - # Skip check_suite completions with no associated PR (e.g. pushes to main): - # there is nothing to review, so the run would be pure waste. - if: >- - github.event_name != 'check_suite' - || github.event.check_suite.pull_requests[0] != null - uses: petry-projects/.github-private/.github/workflows/pr-review.yml@pr-review/stable # NOSONAR(githubactions:S7637) first-party channel ref - with: - agent_ref: pr-review/stable - pr_url: ${{ inputs.pr_url || '' }} - dry_run: ${{ inputs.dry_run || '' }} - force_review: ${{ inputs.force_review || '' }} - secrets: inherit # NOSONAR(githubactions:S7635) first-party trusted reusable \ No newline at end of file diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index 2c64dced..9d3e7401 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -14,11 +14,13 @@ on: paths: - .github/scripts/** - .github/workflows/shell-tests.yml + - .github/workflows/agent-ingress.yml pull_request: branches: [main] paths: - .github/scripts/** - .github/workflows/shell-tests.yml + - .github/workflows/agent-ingress.yml permissions: {}