diff --git a/.github/scripts/dependabot-janitor.sh b/.github/scripts/dependabot-janitor.sh index 65659e6..edc8c43 100755 --- a/.github/scripts/dependabot-janitor.sh +++ b/.github/scripts/dependabot-janitor.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Dependabot janitor: across all wyre-technology mcp-* and node-* repos, auto-merge +# Dependabot janitor: across all mcp-* and node-* repos in scope, auto-merge # Dependabot patch/minor PRs — AND major bumps of dev/CI tooling (eslint, vitest, # typescript, @types/*, GitHub Actions, etc.) — whose CI is green. Major bumps of # RUNTIME dependencies, red CI, conflicts, and code-owner-blocked PRs are reported @@ -8,13 +8,29 @@ # tooling (it doesn't ship at runtime); runtime majors always need a human. # # Requires: gh CLI authenticated via GH_TOKEN (a GitHub App installation token with -# contents:write + pull_requests:write across the org). +# contents:write + pull_requests:write across every org in ORGS). # # Env: -# ORG GitHub org (default: wyre-technology) +# ORGS space-separated GitHub orgs to scan (default: "wyre-technology +# WYRE-AI"). 2026-08-25: the *-mcp fleet moved from being entirely +# under wyre-technology to being split across both orgs (63 +# repos total, only 13 remain under wyre-technology, 50 now under +# WYRE-AI, conduit included) — a single-org $ORG silently covered +# only 13/63 repos with no error, which is almost certainly why +# the dependabot backlog looked like permanent steady-state +# rather than something the janitor was actually working through. +# REPOS entries are now "org/name" pairs so every downstream gh +# call (-R "$repo") targets the repo's ACTUAL org, not a single +# global one — per-repo gh calls already worked across the split +# via GitHub's transfer redirect, but the enumeration step never +# did, since org-level listing doesn't follow transferred repos. +# ORG back-compat single-org override — if set, used as the sole +# entry in ORGS instead of the default two-org list. Prefer ORGS. # DRY_RUN if "true", classify and report but do not approve/merge # EXCLUDE_REPOS space-separated repo names to skip regardless of the -# in-scope regex match below (default: empty). 2026-08-21: +# in-scope regex match below (default: empty). Matched against +# the bare repo name, not "org/name" — a name is excluded on +# whichever org it's found in. 2026-08-21: # used to hold out repos still pinned to the pre-fix # mcp-server-release.yml (vacuous CI — the same bug class # that got this janitor disabled 07-21) until each one's @@ -26,23 +42,33 @@ # the regex. set -uo pipefail -ORG="${ORG:-wyre-technology}" +ORGS="${ORG:-${ORGS:-wyre-technology WYRE-AI}}" DRY_RUN="${DRY_RUN:-false}" EXCLUDE_REPOS="${EXCLUDE_REPOS:-}" work="$(mktemp -d)" for cat in merged majors red pending conflicts blocked errors nocheck; do : > "$work/$cat"; done -# Repos in scope: names ending in -mcp, starting with mcp, or starting with node-, -# minus anything in EXCLUDE_REPOS. +# Repos in scope, across every org in ORGS: names ending in -mcp, starting with +# mcp, or starting with node-, minus anything in EXCLUDE_REPOS. Each entry is +# "org/name" so downstream `-R` calls target the repo's real org directly — +# no repo-name collisions expected across these two orgs, but if one ever +# occurs both entries survive (sort -u dedupes exact "org/name" pairs, not +# bare names), which is the conservative direction to fail in. mapfile -t REPOS < <( - gh api --paginate "/orgs/$ORG/repos?per_page=100" \ - --jq '.[] | select(.archived==false) | .name' \ - | grep -E '(-mcp$|^mcp|^node-)' \ - | { if [[ -n "$EXCLUDE_REPOS" ]]; then grep -vxF -f <(tr ' ' '\n' <<<"$EXCLUDE_REPOS"); else cat; fi; } \ + for _org in $ORGS; do + gh api --paginate "/orgs/$_org/repos?per_page=100" \ + --jq '.[] | select(.archived==false) | .name' \ + | grep -E '(-mcp$|^mcp|^node-)' \ + | sed "s|^|$_org/|" + done \ + | awk -F/ -v exclude="$EXCLUDE_REPOS" ' + BEGIN { n = split(exclude, ex, " "); for (i = 1; i <= n; i++) skip[ex[i]] = 1 } + !($2 in skip) + ' \ | sort -u ) -echo "Scanning ${#REPOS[@]} repositories in scope." +echo "Scanning ${#REPOS[@]} repositories in scope across: $ORGS" [[ -n "$EXCLUDE_REPOS" ]] && echo "Excluded (EXCLUDE_REPOS): $EXCLUDE_REPOS" # Return the leading integer (major version) of a semver-ish string. @@ -81,7 +107,7 @@ is_dev_major() { } for repo in "${REPOS[@]}"; do - prs="$(gh pr list -R "$ORG/$repo" --author 'app/dependabot' --state open \ + prs="$(gh pr list -R "$repo" --author 'app/dependabot' --state open \ --json number,title,mergeable 2>/dev/null)" || { echo "$repo: pr list failed" >>"$work/errors"; continue; } [[ "$(jq 'length' <<<"$prs")" == "0" ]] && continue @@ -103,7 +129,7 @@ for repo in "${REPOS[@]}"; do # CI status. gh pr checks exit codes: 0=all pass, 8=pending, 1=failing, # non-zero+"no checks" => repo has no checks for this PR. - checks_out="$(gh pr checks "$num" -R "$ORG/$repo" 2>&1)"; rc=$? + checks_out="$(gh pr checks "$num" -R "$repo" 2>&1)"; rc=$? if [[ $rc -eq 8 ]]; then echo "$label" >>"$work/pending"; continue; fi if [[ $rc -ne 0 ]]; then if grep -qi 'no checks' <<<"$checks_out"; then @@ -125,7 +151,7 @@ for repo in "${REPOS[@]}"; do # have auto-merged a TS7 major with zero flag -- worse than # node-datto-rmm#46's already-flagged "(no CI)" case, since that one # at least surfaced in the run summary. - buckets_json="$(gh pr checks "$num" -R "$ORG/$repo" --json bucket 2>/dev/null)" + buckets_json="$(gh pr checks "$num" -R "$repo" --json bucket 2>/dev/null)" total="$(jq 'length' <<<"${buckets_json:-[]}" 2>/dev/null || echo 0)" skipping="$(jq '[.[] | select(.bucket=="skipping")] | length' <<<"${buckets_json:-[]}" 2>/dev/null || echo 0)" if [[ "$total" -gt 0 && "$total" == "$skipping" ]]; then @@ -158,9 +184,9 @@ for repo in "${REPOS[@]}"; do fi # Approve (satisfies non-code-owner review requirements) then squash-merge. - gh pr review "$num" -R "$ORG/$repo" --approve \ + gh pr review "$num" -R "$repo" --approve \ -b "Auto-approved by Dependabot janitor: CI green (patch/minor, or dev/CI-tooling major)." >/dev/null 2>&1 - if merge_err="$(gh pr merge "$num" -R "$ORG/$repo" --squash --delete-branch 2>&1)"; then + if merge_err="$(gh pr merge "$num" -R "$repo" --squash --delete-branch 2>&1)"; then echo "$label$flag" >>"$work/merged" else if grep -qiE 'review|code ?owner|protected|required|base branch policy|not mergeable|auto.?merge' <<<"$merge_err"; then diff --git a/.github/workflows/dependabot-janitor.yml b/.github/workflows/dependabot-janitor.yml index efcecce..e8ef46a 100644 --- a/.github/workflows/dependabot-janitor.yml +++ b/.github/workflows/dependabot-janitor.yml @@ -1,11 +1,19 @@ name: Dependabot Janitor # Daily sweep that auto-merges green Dependabot patch/minor PRs across all -# wyre-technology mcp-* and node-* repos. Majors, red CI, conflicts, and PRs -# blocked by required code-owner review are reported, never merged. +# mcp-* and node-* repos in ORGS (wyre-technology and WYRE-AI as of 2026-08-25 +# — the fleet split across both orgs; see dependabot-janitor.sh's own header +# for the full story). Majors, red CI, conflicts, and PRs blocked by required +# code-owner review are reported, never merged. # # Auth: a GitHub App installation token (APP_ID / APP_PRIVATE_KEY org secrets). -# The app must have Contents: Read & write and Pull requests: Read & write. +# The app must have Contents: Read & write and Pull requests: Read & write, +# and — as of the dual-org fix — must be INSTALLED on both wyre-technology +# and WYRE-AI, or token minting below will fail for whichever org it's not +# installed on. Per boss (2026-08-25), the WYRE-AI install is a pending Aaron +# ask already tracked outside this repo — if this workflow starts failing on +# "Mint org-wide installation token" with a WYRE-AI-related error, that's the +# likely cause, not a bug in this file. on: schedule: @@ -34,6 +42,19 @@ jobs: with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.APP_PRIVATE_KEY }} + # NOT yet expanded to WYRE-AI: verified live (2026-08-25) via + # `gh api orgs/WYRE-AI/installations` that the wyre-projects-bot App + # (app_id 3588278, the one APP_ID/APP_PRIVATE_KEY mint tokens for) is + # NOT installed on WYRE-AI — only digitalocean, blacksmith-sh, vanta, + # and two infisical apps are. Adding `WYRE-AI` to `owner:` here before + # that install exists would very likely fail token minting outright + # (untested whether create-github-app-token degrades gracefully to a + # partial-org token or hard-fails on a missing installation — not + # worth risking on the currently-working wyre-technology half to find + # out). Script-side ORGS support (dependabot-janitor.sh) is ready for + # WYRE-AI; this `owner:` line is the one line still gating it. Add + # `WYRE-AI` here once the App install is confirmed, nothing else + # in this file needs to change. owner: ${{ github.repository_owner }} - name: Check out scripts @@ -45,7 +66,16 @@ jobs: - name: Run janitor env: GH_TOKEN: ${{ steps.app-token.outputs.token }} - ORG: ${{ github.repository_owner }} + # Was ORG: ${{ github.repository_owner }} — that pinned this to + # wyre-technology only and silently overrode dependabot-janitor.sh's + # own ORGS default (see that file's header). Removed so the script's + # "wyre-technology WYRE-AI" default takes effect. The token above is + # NOT yet valid for WYRE-AI (App not installed there — see the + # token-minting step's comment), so every WYRE-AI repo will show up + # in this run's Errors section ("pr list failed") until that install + # lands — expected, isolated per-repo, and won't affect wyre-technology + # repos. That's the intended interim state: visible and diagnosable + # instead of silently scanning 13 of 63 repos with no error at all. DRY_RUN: ${{ inputs.dry_run }} BACKLOG_FILE: dependabot-backlog.md # 2026-08-21: all 25 repos originally held out (see task_1786765529531 diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b9ab8..da55f1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,44 @@ here. The format is based on `cancel-in-progress: false` is deliberate — a queued run waits for the in-flight release/publish to finish rather than cancelling it mid-publish. +- **`dependabot-janitor.sh` / `dependabot-janitor.yml`**: added dual-org + support. The *-mcp/node-* fleet moved from being entirely under + `wyre-technology` to being split across `wyre-technology` (13 repos) and + `WYRE-AI` (50 repos, `conduit` included) sometime around 2026-08-24 + evening/night. The janitor's repo enumeration was a single-org API call + (`ORG`, defaulting to `wyre-technology`), so it silently kept scanning + only the 13 repos still there — no error, just 50 of 63 repos never + looked at. Almost certainly the real explanation for a persistent + ~80-108-PR "chronic dependabot backlog" that multiple `scan-mcp-repos` + cycles reported as steady-state review-gating rather than what it + actually was: the janitor never reaching those repos at all. + + `ORG` is now `ORGS` (space-separated, default `"wyre-technology + WYRE-AI"`, with `ORG` kept as a back-compat single-org override). Each + `REPOS` entry is now an `"org/name"` pair rather than a bare name, so + every downstream `gh ... -R` call targets the repo's real org directly. + Verified live: the new enumeration finds 105 repos in scope (85 WYRE-AI + + 20 wyre-technology, using the script's actual `-mcp$|^mcp|^node-` + pattern, broader than just the `*-mcp` fleet) vs. the ~20 the old + single-org call would have found. + + **Not fully live yet — one line still gates it, left in place and + documented rather than silently forced.** The `wyre-projects-bot` App + (the one `APP_ID`/`APP_PRIVATE_KEY` mint tokens for) is confirmed NOT + installed on `WYRE-AI` (verified via `gh api orgs/WYRE-AI/installations` + — only `digitalocean`, `blacksmith-sh`, `vanta-with-task-management`, + and two `infisical` apps are). Adding `WYRE-AI` to the token-minting + step's `owner:` before that install exists risks failing token minting + outright rather than degrading gracefully (untested, and not worth + risking the currently-working `wyre-technology` half to find out) — so + `owner:` is left single-org for now, with the exact one-line change + documented inline for whoever does the App install. Meanwhile `ORGS` + already includes `WYRE-AI`, so every `WYRE-AI` repo will show up in the + run's Errors section (`pr list failed`, an auth failure) until the + install lands — expected, isolated per-repo (no crash, no effect on + `wyre-technology` repos), and turns a previously-invisible gap into a + visible, diagnosable one in the workflow's own summary output. + - **`mcp-server-release.yml`**: the `mcpb` job did not install the MCPB CLI, so it failed on 24 of the 26 repos with a `pack:mcpb` script. Pack scripts shell out to `npx mcpb pack`; only `autotask-mcp` and `blumira-mcp` carry