Skip to content
Back to skills

Release

ASecurity

Release a validated AGRO commit by pushing it to main or master, then monitor the automatic SemVer/GHCR/GitHub Release workflow. TRIGGER when: asked to release, version, ship, cut a release, or verify release artifacts.

  • 40 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added June 11, 2026
ai-agentsbashnodedockergitdatabasedocumentation

Works with

  • cli

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned October 1, 2026

npx -y skills add mifunedev/openharness --skill release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mifunedev-release/badge)](https://www.skillsdirectory.com/skills/mifunedev-release)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: release
description: |
  Release a validated AGRO commit by pushing it to main or master, then
  monitor the automatic SemVer/GHCR/GitHub Release workflow. TRIGGER when:
  asked to release, version, ship, cut a release, or verify release artifacts.
argument-hint: "[--dry-run]"
---

# Release

`.github/workflows/release.yml` owns version allocation and artifact mutation.
The workflow validates every push to `main`, `master`, or `experiment/**` first, then reserves the
`v<version>` tag for the version root `package.json` names, publishes GHCR and
the CLI (or confirms the CLI version already exists), and finally publishes the
GitHub Release. Do not pre-create a release tag, draft, or `release/<version>`
branch.

Root `package.json` holds the release version. A release is a deliberate bump:
an unchanged version gives a clean, green no-op run.

## Artifact set

One release produces, from one build and one commit:

- The canonical npm package `@mifune/agro` (the `agro` executable, from `.agro/cli`).
  The `@mifune/agro` shim source remains at `.agro/cli/legacy/` and
  already-published shim versions remain on the registry. The release path does
  not publish, wait for, or deprecate the shim.
- Four immutable GHCR tags: `ghcr.io/mifunedev/agro:<version>`,
  `:sha-<sha>`, `ghcr.io/mifunedev/agro:<version>`, and `:sha-<sha>`, verified to
  share one manifest digest (`.agro/scripts/verify-release-aliases.sh`), then
  `latest` on both repositories (`.agro/scripts/promote-release-latest.sh`).
- Four GitHub Release assets: `agro.js`, `oh.js`, `get-agro.sh`, and `get-agro.sh`,
  attached before the release is undrafted so
  `releases/latest/download/<asset>` resolves on publication.

## Version sites

A release cut bumps the canonical version in two places, and `version-parity.sh`
fails the build when they drift:

1. `package.json` (root).
2. `.agro/cli/package.json` and `.agro/cli/package-lock.json`.

The retained shim at `.agro/cli/legacy/package.json` keeps its own `version` and
an exact `@mifune/agro` pin that equals that shim version. The shim version does
not have to match a later canonical version. `` checks that
internal coherence.

## Operator prerequisites this repository cannot verify

- The npm token has publish rights for the `@mifune` scope, including the
  `@mifune/agro` package name.
- The GHCR package `mifunedev/agro` is set to public after its first push. A new
  GHCR package is private by default, so `agro sandbox install docker` cannot
  pull it until the operator changes the visibility.
- The compatibility SLA clock starts at the first public AGRO release, that is,
  the first release that publishes `@mifune/agro` and `ghcr.io/mifunedev/agro`.
- The docs-site dispatch token exists. The `notify-docs` job reads the secret
  `AGRO_WEB_DISPATCH_TOKEN` from the release repository (`mifunedev/agro`) and sends
  `repository_dispatch` to the docs repository. When the secret is absent the job
  prints a `::notice::` line and exits green without a dispatch. Issue a fine-grained
  personal access token for the docs repository with **Contents: Read and write**
  (`repository_dispatch` requires `contents: write`; a classic token needs the
  `repo` scope), then store it from a file, never from the command line:

  ```bash
  gh secret set AGRO_WEB_DISPATCH_TOKEN --repo mifunedev/agro < token-file
  ```

- The docs repository target is the repository variable `AGRO_WEB_REPO`, default
  `mifunedev/agro-web`. After the docs repository is renamed, point it at
  the new name:

  ```bash
  gh variable set AGRO_WEB_REPO --repo mifunedev/agro --body mifunedev/agro-web
  ```

## Pre-releases

A pre-release version has the form `MAJOR.MINOR.PATCH-<channel>.<n>`, for example
`0.15.0-minimal.1`. `<channel>` is lowercase (`[a-z][a-z0-9]*`) and is not `latest`.
`minimal` names the `experiment/minimal-core` track. Reserve `rc` for candidates of
the next `main` release.

| Version form | Branches that publish it | GitHub Release | npm dist-tag | GHCR `latest` |
| --- | --- | --- | --- | --- |
| `0.15.0` | `main`, `master` | latest | `latest` | moves |
| `0.15.0-minimal.1` | `main`, `master`, `experiment/**` | pre-release, never latest | `minimal` | never moves |

A stable version on an `experiment/**` push is a green no-op
(`stable-off-release-branch`), so a stable bump merged from `development` never
publishes from an experiment branch.

To cut a pre-release on an experiment branch:

1. Merge `development` into the branch so its `release.yml` supports pre-releases.
2. Set the version in root `package.json`, `.agro/cli/package.json`, and
   `.agro/cli/package-lock.json`.
3. Add a dated `## [<version>] - YYYY-MM-DD` heading to `CHANGELOG.md`.
4. Push the branch. Monitor the run as in step 4, with `--branch <experiment-branch>`.

Install the pre-release:

```bash
npm i -g @mifune/agro@minimal
agro sandbox install docker --version 0.15.0-minimal.1
```

## 1. Resolve the canonical destination

```bash
if git remote get-url upstream >/dev/null 2>&1; then
  REMOTE=upstream
else
  REMOTE=origin
fi
REPO=$(gh repo view "$(git remote get-url "$REMOTE")" --json nameWithOwner -q .nameWithOwner)

if git ls-remote --exit-code --heads "$REMOTE" main >/dev/null 2>&1; then
  TARGET=main
elif git ls-remote --exit-code --heads "$REMOTE" master >/dev/null 2>&1; then
  TARGET=master
else
  echo "No main or master release branch exists on $REMOTE" >&2
  exit 1
fi
SOURCE=$(git branch --show-current)
printf 'Repo: %s · source: %s · release branch: %s\n' "$REPO" "$SOURCE" "$TARGET"
```

## 2. Pre-flight

Require all of the following before a release push:

- The working tree is clean.
- The source commit is pushed to the canonical remote.
- CI for the source commit is green.
- Root `package.json` names the version to publish, `.agro/cli/package.json`
  matches it (`bash .agro/evals/probes/version-parity.sh`), the retained shim
  stays internally coherent (`bash .agro/evals/probes/`), and
  no `v<version>` tag exists yet. An unbumped push is a green no-op that publishes
  nothing. A newly published `@mifune/agro` version is not required.
- `CHANGELOG.md` has a `## [<version>]` section matching that version (the
  workflow falls back to `[Unreleased]` when the section is absent).
- The remote release branch is an ancestor of the source commit, so promotion is
  a fast-forward.

```bash
test -z "$(git status --porcelain)" || { echo "Working tree is dirty" >&2; exit 1; }
git fetch "$REMOTE" "$SOURCE" "$TARGET" --tags
VERSION=$(node -p "require('./package.json').version")
git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null && {
  echo "v$VERSION is already tagged; bump package.json to cut a new release" >&2
  exit 1
}
grep -q "^## \[$VERSION\]" CHANGELOG.md || {
  echo "CHANGELOG.md has no section for $VERSION" >&2
  exit 1
}
SHA=$(git rev-parse "$REMOTE/$SOURCE")
test "$(git rev-parse HEAD)" = "$SHA" || {
  echo "Local $SOURCE is not identical to $REMOTE/$SOURCE" >&2
  exit 1
}
git merge-base --is-ancestor "$REMOTE/$TARGET" "$SHA" || {
  echo "$TARGET has diverged from $SOURCE; reconcile before release" >&2
  exit 1
}
```

If `$ARGUMENTS` contains `--dry-run`, report the resolved repo, source, target,
SHA, clean-tree result, and fast-forward result, then stop without pushing.

## 3. Trigger the release

Promote the exact checked source SHA. The branch push—not a manually created
tag—is the release trigger.

```bash
git push "$REMOTE" "$SHA:refs/heads/$TARGET"
```

The workflow reads the version from root `package.json` on the pushed commit, so
retries always resolve the same version. A retry reuses a same-SHA draft or
published release. When the tag already exists on a different commit, the reserve
step reports the version as already released and every publication job skips —
the run stays green. Bump the version to publish again.

## 4. Monitor and verify

Find the `release.yml` push run for `$SHA` and `$TARGET`, then watch it:

```bash
gh run list --repo "$REPO" --workflow release.yml --branch "$TARGET" \
  --commit "$SHA" --event push --limit 5 \
  --json databaseId,headSha,status,conclusion,url
# Once the matching run appears:
gh run watch <run-id> --repo "$REPO" --exit-status
```

After success, fetch tags and identify the SemVer tag pointing to the exact SHA,
then verify the four immutable image tags, the canonical npm package, the release
assets, and the GitHub Release. The tag carries the `v` prefix; the image tags
do not. A newly published `@mifune/agro` version is not a release gate.
Automatic `notify-docs` dispatch is not the only documentation acceptance path.

```bash
git fetch "$REMOTE" --tags
TAG=$(git tag --points-at "$SHA" \
  | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' \
  | sort -V | tail -1)
test -n "$TAG" || { echo "No SemVer tag found for $SHA" >&2; exit 1; }
gh release view "$TAG" --repo "$REPO" --json assets -q '.assets[].name'
.agro/scripts/verify-release-aliases.sh check \
  "ghcr.io/mifunedev/agro:${TAG#v}" "ghcr.io/mifunedev/agro:${TAG#v}"
npm view "@mifune/agro@${TAG#v}" version
printf 'Images: ghcr.io/mifunedev/{agro,agro}:%s and :sha-%s\n' "${TAG#v}" "$SHA"
```

The asset list must name `agro.js`, `oh.js`, `get-agro.sh`, and `get-agro.sh`.

The canonical mutable/latest branch is `main` when it exists, otherwise
`master`. Immediately before promotion, the workflow freshly reads both remote
refs and promotes the canonical head's versioned image to `latest` by immutable
digest. Stale canonical runs and every noncanonical-branch run skip `latest`;
GitHub's `make_latest` flag uses the same rule after a second fresh check.

After `finalize` succeeds on a real release, `notify-docs` sends
`repository_dispatch` with `event_type: agro-release` and
`client_payload: { ref: <released sha> }` to `AGRO_WEB_REPO`. The docs site's
`pages.yml` reads `client_payload.ref` and rebuilds its mirrored installers from
that commit. Confirm the dispatch landed:

```bash
gh run list --repo "${AGRO_WEB_REPO:-mifunedev/agro-web}" \
  --workflow pages.yml --event repository_dispatch --limit 3 \
  --json databaseId,status,conclusion,createdAt,url
```

A skipped `notify-docs` step with the `::notice::` line means the secret is not
set; the daily schedule in `pages.yml` still refreshes the mirror.

Files in this skill

  • LICENSE1.1 KB
  • SKILL.md6 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…