Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
bd2cc7b
feat(bitbucket): add guarded cloud PR merge
KatalKavya96 Sep 29, 2026
414567f
Merge branch 'main' into feat-bitbucket-cloud-pr-merge
KatalKavya96 Sep 30, 2026
752bd31
fix(bitbucket): align cloud merge with land contract
KatalKavya96 Sep 30, 2026
6dbb905
Merge branch 'main' into feat-bitbucket-cloud-pr-merge
KatalKavya96 Oct 4, 2026
06a4e72
Merge branch 'main' into feat-bitbucket-cloud-pr-merge
KatalKavya96 Oct 5, 2026
1508d62
fix(bitbucket): harden cloud PR merge
KatalKavya96 Oct 5, 2026
dcb994c
fix(release-config): initialise skill before parsing it (#1514)
potiuk Oct 5, 2026
6d388cf
feat(tools/mail-source): add Mailman 3 / Hyperkitty archive backend (…
oscerd Oct 5, 2026
9fb9c96
perf(release-management): wording pass on the release skills (#1517)
potiuk Oct 5, 2026
a67385c
perf(contributor-growth): trim activity-sweep skill routing metadata …
Kaap10 Oct 5, 2026
893b784
perf(release-management): shorter descriptions for five release skill…
potiuk Oct 5, 2026
c01e723
fix(release-rc-cut): route its two GitHub calls through vetted operat…
potiuk Oct 5, 2026
1285355
fix(agent-guard): re-exec under Python 3.11+ when python3 is older (#…
shahar1 Oct 5, 2026
a1665be
chore(vetted-ops): grant release-rc-cut its two operations in Magpie'…
potiuk Oct 5, 2026
6210738
chore(asf.yaml): require review threads to be resolved before merge (…
potiuk Oct 5, 2026
3d4054b
feat(tools): add informational JVM checks 5-7 to maven-artifact-verif…
liwenjie200543 Oct 5, 2026
38ffc60
perf(contributor-growth): trim contributor-to-committer body budget (…
Kaap10 Oct 5, 2026
fc05807
feat(tools/forgejo): add Forgejo/Gitea adapter bridge (part of #310) …
Kaap10 Oct 5, 2026
d4a2bd1
ci(labeler): label PRs on workflow_run, from skills too, and pass lab…
potiuk Oct 5, 2026
459ea69
ci(labeler): count only explicit references when passing labels to is…
potiuk Oct 5, 2026
a0ee13f
perf(contributor-growth): trim nomination body budget (#1489)
Kaap10 Oct 5, 2026
20dd624
fix(bitbucket): report pull request state and source commit in Cloud …
dpol1 Oct 5, 2026
215067f
fix(skill-evals): give template-less eval steps a neutral user prompt…
dpol1 Oct 5, 2026
c99d0e0
fix(setup-preflight): read the local lock under its own keys (#1523)
dpol1 Oct 5, 2026
b286b45
fix(validator): check skill files reached through skills/ symlinks (#…
dpol1 Oct 5, 2026
e455438
docs(agents): open GitHub pages for the user with gh browse (#1529)
potiuk Oct 5, 2026
b61e64a
fix(pr-triage): check every --add-label value in the mark-ready guard…
dpol1 Oct 5, 2026
4a2e534
feat(pr-management-triage): opt-in pre-filter using typed_decision.ch…
onlyarnav Oct 5, 2026
f4515fb
feat(cve-tool-vulnogram): get Vulnogram tokens through browser approv…
potiuk Oct 5, 2026
2063f03
fix(dev): follow skills/ symlinks in check-placeholders on BSD grep t…
potiuk Oct 5, 2026
f82cc2c
Merge main into feat-bitbucket-cloud-pr-merge
potiuk Oct 5, 2026
57ce340
fix(bitbucket): harden the cloud merge pin, timeout and status reporting
potiuk Oct 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .apache-magpie-overrides/tools/vetted-ops/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,13 @@ close_reasons = ["completed", "not planned"]
"label-list",
"gql-pr-review-threads",
]

# release-rc-cut emits every release command for the Release Manager and runs
# none itself. Its only GitHub access is reading the upstream tags (Step 0, to
# refuse an RC tag that already exists) and, after the RM confirms it, the
# planning-issue comment (Step 4). `repo-issue-comment` writes, so it runs
# through `vetted-op` and asks every time.
"release-rc-cut" = [
"tags",
"repo-issue-comment",
]
21 changes: 8 additions & 13 deletions .asf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -185,19 +185,14 @@ github:
# above — squash is the only enabled merge mode, so every
# merge results in a single commit on top of main.
required_linear_history: true
# Do NOT block merge on unresolved review threads. With the
# approval requirement lifted above, this was the one merge gate
# a reviewer could trip by accident: *any* open thread held the
# PR, including a nit the reviewer explicitly marked as
# non-blocking. The observed effect is reviewers resolving their
# own advisory comments purely to unblock the merge, which
# defeats the point of leaving the comment where the author can
# still see it. Unresolved threads remain visible in the PR UI;
# they are simply no longer a hard gate.
#
# Restore alongside `required_pull_request_reviews` above if the
# project later wants threads to gate merge again.
required_conversation_resolution: false
# Block merge until every review thread is resolved. With the
# approval requirement lifted above, an unresolved thread is the
# only signal left that a reviewer's point is still open, and
# nothing stopped a PR from merging past it. Resolving a thread
# is cheap — the author does it after pushing the fix, or the
# reviewer does when a nit is deliberately left as-is — and it
# makes "every point was answered" a gate rather than a hope.
required_conversation_resolution: true
# Do NOT require signed commits. External contributors
# without configured GPG/SSH signing would be unable to
# contribute. Re-enable if/when the project adopts a
Expand Down
400 changes: 397 additions & 3 deletions .github/labeler.yml

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions .github/workflows/labeler-signal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
---
# A doorbell for labeler.yml, and nothing more.
#
# A `pull_request` run holds no privileges, so this one does nothing at all:
# it has no permissions, checks nothing out and runs no code. Its only effect
# is that it completes, which fires labeler.yml's `workflow_run` trigger in the
# default branch's context, where the labeler reads the pull request through
# the API and labels it. `edited` is included so a pull request that starts
# referring to an issue passes its labels on to that issue, and `closed` so a
# merge passes an outside contributor's labels on (see labeler.yml).
name: "Labeler signal"
"on":
pull_request:
types: [opened, reopened, synchronize, ready_for_review, edited, closed]

permissions: {}

jobs:
signal:
runs-on: ubuntu-slim
timeout-minutes: 2
steps:
- name: Signal the labeler
run: echo "labeler.yml runs on this workflow's completion"
181 changes: 149 additions & 32 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,65 +15,182 @@
# specific language governing permissions and limitations
# under the License.
#
# Applies the tool-capability labels (`contract:*` / `substrate:*`) to new
# pull requests from the tool directories they touch. The mapping is
# `.github/labeler.yml`, generated from each tool README's `**Capability:**`
# line by tools/dev/generate-labeler-config.py.
# Labels pull requests from the files they touch, and passes those labels on
# to the issues each pull request closes or refers to with a reference phrase
# ("Part of #N", "Refs #N", "Related to #N"). The mapping is
# `.github/labeler.yml`, generated by tools/dev/generate-labeler-config.py
# from tool READMEs (`contract:*` / `substrate:*`) and skill frontmatter
# (`family:*` / `capability:*`); see docs/labels-and-capabilities.md.
#
# It runs on a schedule rather than on a pull-request event: a scheduled run
# executes in this repository's context, so its token can label fork PRs
# without `pull_request_target`, and no PR event ever starts it. Nothing from
# a PR is checked out or run; the labeler reads the changed-file list and the
# config (from the default branch) through the API.
# Privilege boundary — read this before changing any trigger.
#
# Each run labels the open PRs created since the previous successful run
# started, so a PR is labelled once, shortly after it is opened. Path-based
# labels are a starting point (docs/labels-and-capabilities.md asks for the
# capability the change *implements*), and a label a maintainer removes
# afterwards is not re-added.
# This workflow holds a token that can label pull requests and issues, so it
# never checks out, builds or runs anything from a pull request. It does not
# use pull_request_target. Its triggers are signals only:
# - workflow_run fires when labeler-signal.yml (an unprivileged
# `pull_request` run that does nothing) completes. It always runs this file
# from the default branch, which is the property that makes it safe, and
# labels the pull request the moment it is opened or pushed to.
# - schedule is a daily safety net: it labels any open pull request that
# still has no `family:*` label (a run that failed or was dropped).
# - workflow_dispatch labels one pull request, or runs the safety net.
# The only value taken from the triggering event is the head SHA, checked to be
# 40 hex characters and used solely to find the pull request among the open
# ones. A pull request's body is read only to extract issue numbers, which are
# checked to be issues before any label is added; no text from it reaches a
# command. The labeler action reads the changed-file list and the config (from
# the default branch) through the API.
#
# Who decides which issues get labels: a pull request's body names them, and
# its author can edit the body at any time, even after the merge. So the body
# is trusted only when the author is an OWNER, MEMBER or COLLABORATOR. For
# anyone else the body is never read: their pull request labels only the issues
# its merge actually closed, which GitHub records as the issue's closer and
# nobody can edit afterwards.
#
# Labels are only ever added. A label a maintainer removes is re-added only
# when the pull request is pushed to again and still matches the rule.
---
name: "Tool capability labels"
name: "Pull request labels"
"on":
workflow_run: # zizmor: ignore[dangerous-triggers] -- default-branch code, no PR input; see header
workflows: ["Labeler signal"]
types: [completed]
schedule:
- cron: "17 * * * *"
- cron: "17 3 * * *"
workflow_dispatch:
inputs:
pr:
description: "Label this pull request number (blank: every open pull request without a family label)"
required: false
type: string
permissions: {}
concurrency:
group: tool-capability-labels
group: pull-request-labels
cancel-in-progress: false
jobs:
label:
name: Label new pull requests
name: Label pull requests and their issues
if: >-
github.event_name != 'workflow_run' ||
github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-slim
timeout-minutes: 5
timeout-minutes: 10
permissions:
actions: read # find the previous successful run
contents: read # read .github/labeler.yml and the PRs' changed files
pull-requests: write # add the labels
contents: read # read .github/labeler.yml and the pull requests' changed files
pull-requests: write # add labels to pull requests
issues: write # add the same labels to the issues they close or refer to
steps:
- name: Select pull requests opened since the last run
- name: Select pull requests
id: select
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
EVENT: ${{ github.event_name }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
PR_INPUT: ${{ inputs.pr }}
run: |
set -euo pipefail
since=$(gh api "repos/$REPO/actions/workflows/labeler.yml/runs?status=success&per_page=1" \
--jq '.workflow_runs[0].run_started_at // empty')
if [ -z "$since" ]; then
since=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)
fi
# Dependency and version bumps implement no capability: skip bot authors.
prs=$(gh pr list --repo "$REPO" --state open --limit 100 \
--search "created:>=$since" \
--json number,author --jq '.[] | select(.author.is_bot | not) | .number')
echo "since $since: ${prs:-none}" | tr '\n' ' '
open_prs() {
gh pr list --repo "$REPO" --state open --limit 200 \
--json number,headRefOid,author,labels --jq "$1"
}
case "$EVENT" in
workflow_run)
if ! [[ "$HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::unexpected head SHA"; exit 1
fi
prs=$(open_prs ".[] | select(.headRefOid == \"$HEAD_SHA\" and (.author.is_bot | not)) | .number")
if [ -z "$prs" ]; then # closed: the merged pull request this commit belongs to
prs=$(gh api "repos/$REPO/commits/$HEAD_SHA/pulls" \
--jq '.[] | select(.merged_at != null and (.user.type != "Bot")) | .number')
fi
;;
workflow_dispatch)
if [ -n "$PR_INPUT" ]; then
if ! [[ "$PR_INPUT" =~ ^[0-9]+$ ]]; then
echo "::error::pr must be a pull request number"; exit 1
fi
prs=$PR_INPUT
else
prs=$(open_prs '.[] | select((.author.is_bot | not) and ([.labels[].name | startswith("family:")] | any | not)) | .number')
fi
;;
*)
prs=$(open_prs '.[] | select((.author.is_bot | not) and ([.labels[].name | startswith("family:")] | any | not)) | .number')
;;
esac
echo "pull requests: ${prs:-none}" | tr '\n' ' '
{
echo "prs<<EOF"
if [ -n "$prs" ]; then echo "$prs"; fi
echo "EOF"
} >> "$GITHUB_OUTPUT"

- if: steps.select.outputs.prs != ''
uses: actions/labeler@bf12e9b00b37c5c0ca2b87b79b2daf7891dbda13 # v7.0.0
with:
pr-number: ${{ steps.select.outputs.prs }}

- name: Pass the labels on to linked issues
if: steps.select.outputs.prs != ''
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
PRS: ${{ steps.select.outputs.prs }}
run: |
set -euo pipefail
for pr in $PRS; do
[[ "$pr" =~ ^[0-9]+$ ]] || continue
read -r assoc merged < <(gh api "repos/$REPO/pulls/$pr" --jq '"\(.author_association) \(.merged)"')
labels=$(gh pr view "$pr" --repo "$REPO" --json labels \
--jq '[.labels[].name | select(test("^(family|capability|contract|substrate):"))] | join(",")')
[ -n "$labels" ] || continue
closing=$(gh pr view "$pr" --repo "$REPO" --json closingIssuesReferences \
--jq '.closingIssuesReferences[].number')
case "$assoc" in
OWNER|MEMBER|COLLABORATOR)
# A project member's body is trusted: the issues it closes, and the
# issues it introduces with a reference phrase ("Part of #N",
# "Refs #N", "Related to #N", "Relates to #N", "Follow-up to #N",
# with #N or this repository's issue URL). A passing #N — an
# example, a test case, a link in prose — is not a reference.
mentioned=$(gh pr view "$pr" --repo "$REPO" --json body --jq '.body // ""' \
| grep -oiE "(part of|refs?|references|related to|relates to|follow[- ]up to)[: ]+(#|https://github\.com/${REPO}/issues/)[0-9]+" \
| grep -oE '[0-9]+$' || true)
;;
*)
# Anyone else: never the body, and only once merged. Keep just the
# issues whose recorded closer is this pull request.
if [ "$merged" != "true" ]; then
echo "#$pr: author is $assoc and it is not merged; its closed issues are labelled on merge"
continue
fi
mentioned=""
verified=""
for issue in $closing; do
[[ "$issue" =~ ^[0-9]+$ ]] || continue
closer=$(gh api graphql -F owner="${REPO%/*}" -F name="${REPO#*/}" -F number="$issue" -f query='
query($owner: String!, $name: String!, $number: Int!) {
repository(owner: $owner, name: $name) {
issue(number: $number) {
timelineItems(itemTypes: [CLOSED_EVENT], last: 10) {
nodes { ... on ClosedEvent { closer { ... on PullRequest { number } } } }
}
}
}
}' --jq '[.data.repository.issue.timelineItems.nodes[].closer.number // empty] | map(tostring) | join(" ")' 2>/dev/null || true)
if [[ " $closer " == *" $pr "* ]]; then verified=$(printf '%s\n%s' "$verified" "$issue"); fi
done
closing=$verified
;;
esac
for issue in $(printf '%s\n%s\n' "$closing" "$mentioned" | grep -E '^[0-9]+$' | sort -un | head -20); do
[ "$issue" = "$pr" ] && continue
kind=$(gh api "repos/$REPO/issues/$issue" --jq 'if .pull_request then "pull" else "issue" end' 2>/dev/null || true)
[ "$kind" = "issue" ] || continue
echo "#$pr -> issue #$issue: $labels"
gh issue edit "$issue" --repo "$REPO" --add-label "$labels"
done
done
14 changes: 12 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,20 @@ repos:
- repo: local
hooks:
- id: generate-labeler-config
name: generate-labeler-config (tool READMEs -> .github/labeler.yml)
name: generate-labeler-config (tool READMEs + skill frontmatter -> .github/labeler.yml)
language: system
entry: tools/dev/generate-labeler-config.py
files: ^(tools/[^/]+/README\.md|\.github/labeler\.yml|tools/dev/generate-labeler-config\.py)$
files: ^(tools/[^/]+/README\.md|\.github/labeler\.yml|tools/dev/generate-labeler-config\.py|plugins/magpie-[^/]+/skills/[^/]+/SKILL\.md|skills/[^/]+|docs/labels-and-capabilities\.md)$
pass_filenames: false
# Every label docs/labels-and-capabilities.md defines must have a rule in
# .github/labeler.yml (or an UNMAPPED entry with a reason), and no rule
# may name an undefined label: a label nobody can apply automatically is
# how pull requests ended up unlabelled.
- id: check-labeler-coverage
name: check-labeler-coverage (every taxonomy label has a labeler rule)
language: system
entry: tools/dev/generate-labeler-config.py --check-coverage
files: ^(tools/[^/]+/README\.md|\.github/labeler\.yml|tools/dev/generate-labeler-config\.py|plugins/magpie-[^/]+/skills/[^/]+/SKILL\.md|skills/[^/]+|docs/labels-and-capabilities\.md)$
pass_filenames: false
# Workspace-level static checks. Iterate over every uv-workspace
# member declared in the root `pyproject.toml`'s
Expand Down
7 changes: 7 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,13 @@ to a home-dir path and update the tool to read from there.
- **Always open PRs with `gh pr create --web`** so the human reviewer can check the title,
body, and the generative-AI disclosure in the browser before submission. Pre-fill `--title`
and `--body-file` (including the Gen-AI disclosure block) so they only need to review, not edit.
- **Open a GitHub page for the human with `gh browse`, never `open <url>`.** The sandbox blocks
macOS `open` (Launch Services and Apple Events: error `-10822`), while `gh` runs outside it
(`sandbox.excludedCommands`) and `gh browse` is in `permissions.allow`, so it opens the page with no
prompt. Use `gh browse <PR-or-issue-number> -R <repo>`, `gh browse <path> -R <repo>` for a file, and
`gh browse <commit-SHA> -R <repo>` for a commit. Run it as a bare command: a pipe, `$(…)` or a
redirection puts `gh` back in the sandbox, where it fails. For a page `gh browse` cannot address,
give the user `! open <url>` to run themselves.
- **Stack a series of dependent PRs with GitHub's stacked PRs — only with write access to `<upstream>`.**
A stacked PR's base is the previous PR's branch, and a PR can only target a branch in the repository
it is opened against, so every branch of a stack must be pushed to `<upstream>` itself, never to a fork.
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ Good entry points, in rough order of ramp-up cost:
The label link above is always current, and the backlog is
substantial. Two broad clusters recur:
- **Tool / adapter bridges** — JIRA write path, Bugzilla, IMAP / mbox
concrete wiring, GitLab, Mailman 3 / Hyperkitty, Discourse, Zulip,
concrete wiring, GitLab, Discourse, Zulip,
Matrix, Forgejo, OSV.dev, Pagure.
- **Agent-CLI harness adapters** — Codex, Gemini, local-LLM, Cursor,
Aider, gh-copilot, Goose, Amazon Q, Junie, OpenHands.
Expand Down
Loading