An agent skill that takes committed work through a gate before it leaves the machine: adversarial review, the tests, lint and docs commands the repository declares, and the documentation the change made stale. Only then does it push, open the change request, wait for CI and answer every review.
It ships adapters for GitHub pull requests and GitLab merge requests. The gate is the same on both.
npx skills@latest add https://github.com/Thurbeen/publish \
--skill publish --agent universal claude-code --global --yes--global covers every repository you publish from. universal puts the one
real copy in ~/.agents/skills/publish and links each other agent you name to
it, so an update lands everywhere at once. Swap claude-code for any agent
skills supports, but keep universal
and at least one more: with --yes and a single target, the CLI copies instead
of linking. Nothing is compiled or put on PATH; the skill is prose an agent
reads.
On "push this", "ship it", "publish", "open a PR", "get this merged", and on any task whose instructions say to publish its own work.
Not when the repository ships its own publish, ship or release skill, and
not when a gate tool is already installed and configured in that clone. Both
push and open a change request too, and running two is how a branch ends up
with two.
The blue loop carries every fix back to review, and a dot on a phase means it has an exit in the side panel. The animation follows one run: each phase lights as it is reached, a feedback fix takes the loop back to review, everything from review on runs again, and the run ends at the report with passed. GitHub shows the GIF matching your theme and plays it according to your animated-image autoplay setting, which follows the system's reduced-motion preference by default. The linked SVG reads fully when static and turns its motion off where reduced motion is preferred.
- Review is the product. It reads the repository's own rules first, keeps
only findings with a concrete failure scenario, and loops until a round finds
nothing, five rounds at most. Measured against the tool this replaces, it
produced the large majority of the fixes. Method:
review.md. - Every step reports one of four statuses:
passed,failed,skippedornot-applicable. The verdict ispassedonly when every step ispassedornot-applicable. A step that could not run, a red pipeline and a pipeline still running all block. - The change request body has four headings, in order:
## Intent,## What Changed,## Risk Assessment,## Testing. It carries no machine attestation; the checked head and results go to the requester. Frontend changes add visual evidence under## What Changed: a before/after GIF when motion or interaction matters, otherwise paired before/after screenshots. An unavailable prior state or a change that is not visually observable is disclosed with the closest honest verification instead of invented media. - Infrastructure changes, including infrastructure directories in mixed
repositories, get one plan-review comment built from current-head preview
evidence, updated on every re-publish. Missing or stale plans block, and the
skill never applies anything. Procedure:
infra-plan.md. - Feedback is answered, not skimmed. Every review, thread and bot summary
is fixed or answered with evidence, then resolved. Late review bots get
feedback.wait, and a point still raised after two rounds stops the run. On a re-publish it also runs before review. Details:feedback.md.
Put .publish.yaml or .publish.yml at the repository root to declare the
gate explicitly. .publish.yaml remains authoritative when both exist:
version: 1
forge: github
base: main
review:
rules:
- AGENTS.md
gate:
- name: lint
run: just lint
fix: just fmt
- name: test
run:
- cargo nextest run --all
- cargo test --doc
instructions: "The sqlite tests share one port, so a bind failure means an earlier run is still up. Stop it and re-run."
ci:
required: true
timeout: 30m
feedback:
wait: 15m| key | required | meaning |
|---|---|---|
version |
yes | 1. |
forge |
no | github or gitlab. Default: matched from the origin remote's host. |
base |
no | Branch to rebase onto and target. Default: the remote's default branch. |
review.rules |
no | Extra files the review must read. |
gate |
yes | Ordered list of steps, run in the order written. |
gate[].name |
yes | The step's name in the final report. Free text. |
gate[].run |
yes | One command, or a list run in order, from the repository root. |
gate[].fix |
no | Applies the mechanical fixes, once, before a re-run. |
gate[].instructions |
no | Text handed, as written, to whoever runs, reads or fixes the step. |
ci.required |
no | Default true. false declares the repository has no CI. |
ci.timeout |
no | Default 30m. |
feedback.wait |
no | Default 15m. How long to keep reading feedback after CI is green. |
gate: [] declares no mechanical gate and records not-applicable. With
neither declaration file, the skill derives the gate from agent and contributor
instructions, pull-request or merge-request CI workflows, task runners,
manifests and tool configuration. It runs those discovered commands through
the same phase-4 validation and reports their sources as discovered:<files>.
If the complete search finds no native mechanical check, the gate is
not-applicable; missing publish config alone never blocks. Full contract:
gate.md.
npx skills add installs skills/publish/ and nothing else, so everything the
skill relies on lives there.
tests/skill-self-contained.test.mjs
keeps it that way.
npm testThe suite needs Node 22 or later, jq and glab on PATH. It runs the
documented jq filters against recorded forge responses, and drives the real
glab CLI against a local HTTP server, so no forge credentials are used.
CI exposes two stable checks for branch protection on main:
All Checksneeds every other job inci.ymland fails if any failed, was cancelled or was skipped.PR Titlerequires a conventional-commit title, because squash merge makes it the commit onmain.
scripts/render-workflow-gif.sh regenerates the README's light and dark GIFs
from assets/workflow.svg; it needs chromium and ffmpeg.
MIT. See LICENSE.
