Install
$ agentstack add skill-cboone-agent-harness-plugins-release ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Release
Prepare a versioned release or Claude Code marketplace catalog state tag: analyze commits, update release files, create a release commit, tag locally, and optionally publish a GitHub Release.
Options
The user may provide these options inline:
- --major: Force a major version bump regardless of commit analysis
- --minor: Force a minor version bump regardless of commit analysis
- --patch: Force a patch version bump regardless of commit analysis
- --dry-run: Preview all changes without modifying any files, committing, or tagging
Workflow
1. Pre-Flight Checks
Run these commands in parallel to understand the current state:
# Check for uncommitted changes
git status --porcelain
# Get current branch name
git branch --show-current
# List existing version tags, sorted by version
git tag --list 'v*' --sort=-version:refname
# List existing Claude Code marketplace catalog state tags
git tag --list 'catalog-M*-m*-p*-n*' --sort=-creatordate
# Get today's date
date +%Y-%m-%d
# Check for a release workflow that publishes GitHub Releases automatically
if [ -d .github/workflows ]; then
for f in .github/workflows/*.yml .github/workflows/*.yaml; do
[ -f "$f" ] || continue
# Tag-triggered workflows: a `tags:` trigger plus a list entry like
# `- "v*"` or `- catalog-*`.
has_tag_trigger=0
if grep -q 'tags:' "$f" && grep -qE "^[[:space:]]*-[[:space:]]+['\"]?(v[*[0-9]|catalog-)" "$f"; then
has_tag_trigger=1
fi
# Marketplace push-to-main automation: workflow invokes the canonical
# catalog state computation, publishes a GitHub Release, AND triggers
# on push to the default branch (main or master). All three are
# required. The compute-catalog-state and gh release create checks
# rule out partial automation (e.g., a validation workflow that
# computes the catalog state without tagging or releasing). The push
# trigger check rules out workflow_dispatch-only or PR-only workflows
# that happen to mention both strings: those will not run when a
# commit lands on main, so deferring to them would silently skip
# local catalog tagging and leave nothing tagged or released. The
# branches:/main match must occur *under* push: rather than under a
# sibling key like pull_request: -- otherwise a workflow with
# `pull_request: branches: [main]` would falsely match. The awk
# script tracks indentation to scope branches: matches to the push:
# block, and handles both inline (`branches: [main]`) and YAML-list
# (`branches:` followed by `- main`) forms.
has_marketplace_push_to_main=0
if grep -q 'compute-catalog-state' "$f" && grep -q 'gh release create' "$f" && awk '
function leading_ws(s) {
match(s, /^[[:space:]]*/)
return RLENGTH
}
{
# When indentation falls back to the push: level (or shallower),
# we have left the push: block. Reset state before running other
# rules on this line so a sibling key (e.g. pull_request:) does
# not pick up branches: matches inside push:.
if (in_push && $0 !~ /^[[:space:]]*$/ && leading_ws($0) \t`. Preserve the workflow kind, because marketplace catalog releases handle the two kinds differently. If a workflow matches both kinds, the loop reports `marketplace-push-to-main`, which is the more specific automation and the only writer of marketplace catalog tags:
- **Tag-triggered workflows**: a `tags:` trigger plus a YAML list entry whose value starts with `v` followed by `*`, `[`, or a digit (e.g., `- "v*"`, `- "v[0-9]+.[0-9]+.[0-9]+"`), or starts with `catalog-`. Anchoring to list-item form avoids false positives from action refs like `actions/checkout@v4`.
- **Marketplace push-to-main workflows**: a workflow that invokes `bin/compute-catalog-state`, runs `gh release create`, and triggers on push to the default branch (`main` or `master`). All three signals together mark end-to-end automation that tags and publishes on push to the default branch. A workflow that only computes the catalog state (e.g., for validation) does not qualify, and neither does a workflow_dispatch-only or PR-triggered workflow that happens to invoke both, since neither will run when a commit lands on main.
Detection is best-effort and may miss inline list forms (e.g., `tags: ['v*']`) or other exotic patterns. If the detection seems wrong, ask the user to confirm. This workflow kind affects M4-M8 for marketplace releases and step 11 for SemVer releases. For marketplace releases, only a **marketplace push-to-main workflow** skips local catalog tagging; a **tag-triggered workflow** still needs M7 to create the exact `catalog-*` tag so pushing that tag can fire the workflow.
**Abort conditions:**
- If the working tree is dirty (uncommitted changes exist), tell the user to commit first (suggest `/commit`) and stop.
- If this is not a git repository, report the error and stop.
### 2. Detect Project Type
Use the detection rules in `./references/project-types.md` to determine the project type. Check in priority order:
1. Claude Code marketplace (`.claude-plugin/marketplace.json` exists and has a top-level `plugins` array)
1. Go CLI (`go.mod` + `cmd/` or `.goreleaser.yml`/`.goreleaser.yaml`)
1. Go library (`go.mod` without main-package indicators)
1. Generic (fallback)
Report the detected type to the user. If the detection is ambiguous (e.g., multiple indicators conflict), ask the user to confirm.
If the project is a Claude Code marketplace, use the marketplace release flow below instead of the SemVer flow in steps 3-11.
### Claude Code Marketplace Release Flow
Claude Code marketplace repos use SemVer for individual plugin `version` fields, but the marketplace `metadata.version` is a catalog state tag, not SemVer.
The catalog state tag format is:
```text
catalog-M-m-p-n
Compute it from .claude-plugin/marketplace.json .plugins[].version exactly:
M: sum of all plugin major versions.m: sum of all plugin minor versions.p: sum of all plugin patch versions.n: number of marketplace plugins.- Do not normalize or carry between components.
M1. Find Last Marketplace Release
Find the most recent marketplace catalog tag:
git tag --list 'catalog-M*-m*-p*-n*' --sort=-creatordate | head -1
If no catalog tags exist, this is the first marketplace release.
M2. Analyze Commits and Plugin Versions
Analyze commits since the last catalog tag, or all commits if no catalog tag exists:
git log ..HEAD --format='%H %s' --no-merges
For the first marketplace release:
git log --format='%H %s' --no-merges
Use the conventional commit analysis from step 4 to summarize changes. Identify which plugin directories changed and ensure each changed plugin gets an appropriate SemVer bump in both plugins//.claude-plugin/plugin.json and the matching marketplace entry. If those plugin version bumps are already present, preserve them.
Abort condition: If there are no commits since the last catalog tag, report that there is nothing to release and stop.
Confirm the individual plugin version changes with the user before modifying files. If --dry-run was specified, do not modify files; report the plugin version changes that would be made, the catalog state that would result, and stop after the dry-run pre-tag review in M5.
M3. Compute Catalog State
After plugin versions are final, compute the catalog state from marketplace plugin versions. Prefer the project's helper script when present so the skill stays in sync with whatever the repository considers canonical:
if [ -f bin/compute-catalog-state ]; then
if [ -x bin/compute-catalog-state ]; then
bin/compute-catalog-state
else
# Helper exists but lost its executable bit. Fail loudly rather than
# falling back to the inline jq: the helper is the single source of
# truth that bin/validate-plugins and the release workflow consume,
# and silently bypassing it would let /release publish a tag the
# validator and CI then reject.
echo "bin/compute-catalog-state exists but is not executable. Run 'chmod +x bin/compute-catalog-state' (and commit the mode bit) before re-running /release." >&2
exit 1
fi
else
jq -r '
def parse_version:
capture("^(?[0-9]+)\\.(?[0-9]+)\\.(?[0-9]+)$")
| {
major: (.major | tonumber),
minor: (.minor | tonumber),
patch: (.patch | tonumber)
};
[.plugins[].version | parse_version] as $versions
| "catalog-M\($versions | map(.major) | add)-m\($versions | map(.minor) | add)-p\($versions | map(.patch) | add)-n\($versions | length)"
' .claude-plugin/marketplace.json
fi
The inline fallback applies only when the helper is absent (e.g., older repos that have not adopted bin/compute-catalog-state). It uses strict MAJOR.MINOR.PATCH parsing (matching bin/validate-plugins) so malformed versions fail loudly instead of silently producing an incorrect tag.
Update .claude-plugin/marketplace.json metadata.version to exactly the computed catalog state.
M4. Reconcile Existing Catalog State Tag
Before any commit or tag operation, check whether the exact catalog state tag already exists, locally and (if a remote is configured) on the remote, and reconcile against HEAD.
Compare commit SHAs, not just the tag name. The catalog-state format is sum-based and not collision-free: two different plugin-version mixes can produce the same component sums (for example, +1 minor on plugin A paired with -1 minor on plugin B). Treating any same-named tag as "already released" would silently drop a real catalog change. Comparing SHAs lets the skill idempotently skip true repeats while loudly aborting on real collisions.
A same-named tag at a different commit does not always mean a collision, though. The release workflow legitimately reuses an existing catalog tag when later commits leave every plugin's version unchanged (for example, a docs-only follow-up after a marketplace bump). M4 must mirror that logic so /release does not falsely report a collision and tell the user to bump versions when there is genuinely nothing to release.
head_commit="$(git rev-parse HEAD)"
# Annotated tags expose the commit they point to via the peeled refspec
# (^{}). Lightweight tags expose the commit directly. Try both locally.
local_commit="$(git rev-parse -q --verify "refs/tags/CATALOG-STATE^{commit}" 2> /dev/null || true)"
# Only consult the remote when one is configured. Repos without an
# `origin` remote (preparing a release locally and publishing later) must
# not fail here just because `git ls-remote origin` would error out.
remote_commit=""
if git remote get-url origin > /dev/null 2>&1; then
# Capture ls-remote's output and exit status separately. Piping straight
# into `cut` would mask auth/network failures: cut succeeds on empty
# input, so an authentication error or a transient network failure
# would leave remote_commit empty and silently fall back to the local
# tag. That can mask an already-published remote CATALOG-STATE tag and
# surface the conflict only when the eventual `git push` fails.
if ! ls_remote_output="$(git ls-remote origin "refs/tags/CATALOG-STATE^{}" "refs/tags/CATALOG-STATE" 2>&1)"; then
{
echo "git ls-remote origin failed:"
printf '%s\n' "${ls_remote_output}"
echo
echo "Cannot verify whether catalog state CATALOG-STATE is already published on origin."
echo "Resolve the network or authentication issue and re-run /release rather than"
echo "proceeding with a possibly-stale local view of remote tags."
} >&2
exit 1
fi
# Prefer the peeled refspec (annotated tags); fall back to the unpeeled
# refspec (lightweight tags). Both are queried in one ls-remote call.
remote_commit="$(printf '%s\n' "${ls_remote_output}" | awk '$2 == "refs/tags/CATALOG-STATE^{}" {print $1; exit}')"
if [[ -z "${remote_commit}" ]]; then
remote_commit="$(printf '%s\n' "${ls_remote_output}" | awk '$2 == "refs/tags/CATALOG-STATE" {print $1; exit}')"
fi
fi
# Prefer the remote tag whenever it exists. Published tags are
# authoritative; a stale or recreated local tag pointing at HEAD must
# not mask a remote ref pointing at a different commit. Track whether
# the matching tag came from the remote or only from the local clone:
# a local-only tag is not proof that the catalog state was published.
existing_commit=""
existing_tag_source=""
if [[ -n "${remote_commit}" ]]; then
existing_commit="${remote_commit}"
existing_tag_source="remote"
elif [[ -n "${local_commit}" ]]; then
existing_commit="${local_commit}"
existing_tag_source="local"
fi
Cases:
- No existing tag (
existing_commitempty): proceed. - Remote tag exists at
HEAD(existing_tag_source=remote): do not retag. If no release workflow was detected, continue to M8c so the skill can verify or create the GitHub Release for the existing remote tag; a previous manual release may have pushed the tag and failed beforegh release create. If a release workflow was detected, report that the catalog tag is already on the remote and the workflow owns GitHub Release publication; do not push the tag again. - Only a local tag exists at
HEAD(existing_tag_source=local): the catalog state is already tagged locally, but it is not known to be published. Do not report that there is nothing to release. Continue to M5-M8, skip creating a replacement tag in M7, and publish the existing local tag if the user chooses to publish. If no remote is configured, M8 will stop with the normal "configure a Git remote" guidance. - Tag exists at a different commit: distinguish "catalog state genuinely unchanged" from "real collision" by comparing the marketplace plugin versions at the tagged commit against
HEAD's. This is the same comparison.github/workflows/release.ymlperforms:
```bash taggedversions="$(git show "${existingcommit}:.claude-plugin/marketplace.json" 2> /dev/null | jq -ec '[.plugins[] | {name, version}]' || echo '')" head_versions="$(jq -ec '[.plugins[] | {name, version}]' .claude-plugin/marketplace.json)"
if [[ -n "${taggedversions}" && "${taggedversions}" == "${head_versions}" ]]; then # Plugin versions are identical, so the catalog state is unchanged. # Do not retag. If no release workflow was detected, continue to M8c # so the skill can create a missing GitHub Release for the existing # remote tag. Otherwise report that the release workflow owns publication. : else # Real catalog-state collision; abort with the message below. : fi ```
If tagged_versions is empty (the tagged commit is not reachable locally, common when the branch lacks remote history), treat the situation as a collision rather than silently proceeding. The user can fetch the tag and re-run, or bump a plugin to break the apparent collision.
For the collision case, present this to the user:
Catalog state tag CATALOG-STATE already exists at a different commit
with different plugin versions.
Existing tag points to:
Current commit:
This is a catalog-state collision: the marketplace plugin versions
changed in a way that produces the same per-component sums as a
previously-released catalog state. The format is sum-based and not
collision-free.
To resolve, bump one plugin's version by an additional patch so the
marketplace produces a unique catalog state, then re-run /release.
Do not create another tag or choose a different state tag automatically. The user must change plugin versions to break the collision.
M5. Pre-Tag Review
Build a final review and wait for explicit user approval. The wording depends on which release workflow kind was detected in step 1.
If M4 found an existing catalog state tag to reuse and M3 produced no release-file changes, skip the pre-tag revi
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: cboone
- Source: cboone/agent-harness-plugins
- License: MIT
- Homepage: https://github.com/cboone/agent-harness-plugins
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.