Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

workflows

Reusable GitHub Actions workflows and composite actions for the fleet (33 chrischall repos + 6 nullnet-app repos). Consumers reference @main.

Component Kind Consumers
.github/workflows/reusable-pr-auto-review.yml reusable workflow all
.github/workflows/reusable-auto-merge.yml reusable workflow all
.github/workflows/reusable-claude.yml reusable workflow all
.github/workflows/reusable-mcp-ci.yml reusable workflow node repos
.github/workflows/reusable-cloudflare-deploy.yml reusable workflow web repos (OpenNext → Cloudflare Workers)
.github/workflows/reusable-mcp-connector-deploy.yml reusable workflow MCP repos with a hosted connector (plain wrangler deploy)
.github/workflows/reusable-fly-deploy.yml reusable workflow repos with a Fly.io backend
.github/workflows/reusable-dependabot-lockfix.yml reusable workflow repos with derived lockfiles dependabot can't refresh
.github/actions/arm-gate composite action bespoke-CI repos (gradle, swift)
templates/ci-gradle.yml starter template Gradle/KMP repos
templates/dependabot-lockfix-{npm,gradle}.yml stub templates repos with lockfix set in fleet.json
.github/actions/mcp-publish composite action MCP publishers
.github/actions/install-mcp-publisher composite action via mcp-publish

The pipeline contract: non-release PR → auto-review emits a mandatory pass|warn|fail verdict → pass or warn adds ready-to-merge → label arms native auto-merge and fires deferred CI (the required check) → merge on green. Deferred CI blocks un-armed PRs in one of two gate modes: legacy fail (the required ci / ci job fails red until armed) or status (a yellow ci-gated: pending commit status blocks instead and the ci / ci job is skipped while un-armed — no misleading green for a build that never ran, and red then only ever means a real failure). Status mode needs the stub to grant statuses: write and the ruleset to require the ci-gated context (scripts/update-ruleset.sh <repo> ci-gated --execute); flip both together. Status mode also suppresses the duplicate CI run a non-arming label would trigger on an already-armed PR (#12) — unsafe to suppress in fail mode, where a green skip would overwrite a legitimately red check on the same SHA. Any review that surfaced findings — a warn/fail verdict, or a pass whose structured output still lists nits — also opens or updates a per-PR auto-review-followup issue holding every finding (linked from the verdict comment): pass/warn still auto-merge — the issue carries the nits forward — while fail keeps a human in the loop. Release-please PRs follow the same gate, except the review is triggered by adding release-ready (not on open): release-ready starts the review, the review's pass/warn adds ready-to-merge, and only then does deferred CI run — so CI never runs ahead of a successful review.

mcp-publish is a composite action (not a reusable workflow) on purpose: npm trusted publishing and mcp-publisher validate the OIDC token's workflow identity, which must remain the consuming repo's own release-please.yml.

arm-gate is the deferred-merge gate as a composite action so bespoke CI jobs (Gradle/KMP, Swift) — which carry repo-specific build steps and so can't call a reusable workflow — still get the one load-bearing rule centrally: an un-armed PR FAILS the required ci check rather than skipping it (a skipped required check counts as satisfied and would let the merge button go live before CI ran). Drop it in as the first step of the CI job; templates/ci-gradle.yml is a ready-to-adapt Gradle/KMP starting point that uses it.

reusable-cloudflare-deploy.yml standardizes shipping a Next.js web/ app to Cloudflare Workers via OpenNext (npm ciopennextjs-cloudflare builddeploy), authed with a CLOUDFLARE_API_TOKEN repo secret + a non-secret account-id input. An optional java-version provisions a Temurin JDK first for apps with a JVM/Gradle prebuild (a shared KMP engine). Onboard a repo by copying templates/deploy-web.yml (swap __ACCOUNT_ID__); see docs/cloudflare-web-deploy.md. Live consumers: allotmint-clients/web (with a JDK prebuild) and curtaincall/web (plain).

reusable-mcp-connector-deploy.yml is the other Cloudflare deploy: an MCP server's hosted connector Worker, deployed with a plain wrangler deploy from the repo root — no framework build, no web/. It takes a ref so a release deploys the released source rather than whatever main happens to be, and pairs with reusable-fly-deploy.yml for connectors that also run a Fly backend (a Worker cannot execute a binary, so gogcli-mcp runs the gog CLI on Fly).

Wire the automatic path into release-please.yml gated on release_created == 'true'; copy templates/deploy-connector.yml for the on-demand workflow_dispatch path. Both jobs pass their deploy token explicitly (never secrets: inherit, which would hand a deploy workflow every secret the repo holds, RELEASE_PAT included) and scope themselves to contents: read. Both warn-and-pass when their token is absent, so a missing secret never reports an otherwise-good release as broken. When a repo deploys both halves, deploy Fly first and make the Worker job needs: it.

These exist because hand-deployed connectors drift: one had drifted far enough to keep serving a tool schema its repo had already replaced.

reusable-dependabot-lockfix.yml regenerates derived lockfiles on dependabot PRs and pushes them back with the release PAT (so CI retriggers), for bumps dependabot can't fully materialize itself: npm repos whose root package-lock.json embeds a file: package the security updater bumps in isolation, and Gradle/KMP repos whose kotlin-js-store/yarn.lock only a gradle run can refresh. Call from a stub on pull_request_target (types: [opened, synchronize]) passing the repo's release PAT; runs are double-guarded to PRs both authored and triggered by dependabot[bot], and the PR head is checked out without credentials so the lockfix command never sees the PAT. The stub must grant contents: read — a permissions: {} caller startup-fails, since the called job requests contents: read for checkout. Onboard a repo by setting lockfix: npm|gradle in fleet.json and running scripts/rollout.sh; off-board by clearing lockfix there and deleting the rendered stub, in that order — the file alone comes back on the next rollout. Live consumer: curtaincall (gradle). untappd-mcp was the npm one until its file:-linked package moved to its own repo and the derived lockfile stopped existing (untappd-mcp#105, #83).

Rollout tooling: fleet.json (per-repo parameters), scripts/rollout.sh (stub-conversion PRs), scripts/update-ruleset.sh (required-check rename). Design: docs/superpowers/specs/2026-06-12-fleet-reusable-workflows-design.md.

docs/fleet-conventions.md is the canonical home for the technical conventions the MCP repos share — publishing constraints, bundling and .mcpb rules, stdio, versioning guards, write-verification, transport archetypes, testing traps. Link to it from a repo's CLAUDE.md instead of copying a section into it.

About

Reusable GitHub Actions workflows for the fleet: auto-review, auto-merge, CI, release

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages