Create a SemVer annotated git tag on main by inferring the bump type (major/minor/patch) from conventional commits since the last tag, push the tag to remote, and optionally create a GitHub Release via gh. Global and project-agnostic. Trigger when the user says "tag main", "create a tag", "tag release", "cut a release", "release v1.2.3", "tag version", "bump version and tag", "create a release", "release this", or "/tag". SKIP when the user is asking about Docker image tags or non-git tagging.
Scanned 6/9/2026
Install via CLI
openskills install ada-ggf25/AI-Tools---
name: tag
description: Create a SemVer annotated git tag on main by inferring the bump type (major/minor/patch) from conventional commits since the last tag, push the tag to remote, and optionally create a GitHub Release via gh. Global and project-agnostic. Trigger when the user says "tag main", "create a tag", "tag release", "cut a release", "release v1.2.3", "tag version", "bump version and tag", "create a release", "release this", or "/tag". SKIP when the user is asking about Docker image tags or non-git tagging.
---
# Git Tag — SemVer release skill
Creates a SemVer annotated tag on `main` by reading conventional commits since the last
tag, inferring the correct bump (major / minor / patch), showing a full proposal for
approval, then tagging, pushing, and optionally opening a GitHub Release. Never tags
silently — the user always approves the version first.
## Accepted flags (parsed from the user's invocation text)
| Flag | Effect |
|---|---|
| `--major` | Force a major bump regardless of commit types |
| `--minor` | Force a minor bump regardless of commit types |
| `--patch` | Force a patch bump regardless of commit types |
| `--pre <suffix>` | Append a pre-release suffix (e.g. `--pre rc.1` → `v1.3.0-rc.1`) |
| `--release` | Create a GitHub Release after pushing without prompting |
| `--no-release` | Skip GitHub Release creation entirely without prompting |
| `--draft` | Create the GitHub Release as a draft (implies `--release`) |
| `--no-push` | Create the tag locally only; do not push to the remote |
If the user provides a bare version string in the invocation (e.g. `/tag v2.0.0`), use it
verbatim and skip bump inference entirely.
## Procedure
### 1. Parse flags and version hint
Extract all flags above from the invocation text. If a string matching `v?\d+\.\d+\.\d+`
(with an optional pre-release suffix) is present, treat it as an explicit version override
and jump to step 6 after confirming it does not already exist.
### 2. Pre-flight checks (run in parallel)
- `git fetch --tags origin` — pull latest tags and remote refs before any inference.
- `git status --short` — working tree must be clean.
- `git rev-parse main` vs `git rev-parse origin/main` — check local main is up-to-date.
- `gh run list --branch main --status completed --limit 1 --json conclusion,workflowName,url`
— check the latest completed CI run on main. If `gh` is available and authenticated,
verify the conclusion is `success`.
If the working tree is dirty: stop and tell the user to commit or stash first.
If local `main` is behind `origin/main`: offer to run `git pull --ff-only origin main`
and continue; stop if they decline.
If the latest CI run on main is not `success`: **warn clearly** — show the workflow name
and URL — and require explicit confirmation to proceed ("CI is failing on main. Tag
anyway? This may release broken code."). If `gh` is absent or unauthenticated, skip the
CI check and note it was skipped.
### 3. Find the last SemVer tag
Run `git describe --tags --match 'v[0-9]*' --abbrev=0 main 2>/dev/null`.
- If a tag is found: record it as `LAST_TAG`; parse its `M.N.P` triple.
- If no tag exists: set `LAST_TAG` to empty and base version to `0.0.0`.
### 4. Gather commits since the last tag
Run `git log ${LAST_TAG:+$LAST_TAG..}main --oneline --no-merges`.
If no commits exist since the last tag: inform the user ("nothing new since `<LAST_TAG>`")
and stop.
### 5. Infer the SemVer bump
Apply the first matching rule in priority order:
| Condition | Bump |
|---|---|
| Any commit subject ends with `!` OR any commit body contains `BREAKING CHANGE:` | **major** |
| Any commit type is `feat` | **minor** |
| Any other conventional-commit type (`fix`, `perf`, `refactor`, `docs`, `test`, `build`, `ci`, `chore`) | **patch** |
| No conventional-commit pattern detected | **patch** (conservative default; note this to the user) |
If `--major`, `--minor`, or `--patch` was given, use it unconditionally.
Compute the new version by incrementing the chosen component and zeroing lower ones:
- major → `(M+1).0.0`
- minor → `M.(N+1).0`
- patch → `M.N.(P+1)`
Append the pre-release suffix if `--pre <suffix>` was given: `vM.N.P-<suffix>`.
### 6. Show proposal and wait for approval
Present a summary block before doing anything:
```text
Proposed tag : v<new_version>
Bump reason : <major feat! | minor feat | patch fix | … | explicit>
Last tag : <LAST_TAG or "none (first release)">
Commits : <N> commit(s) since last tag
Push : origin [or "local only" if --no-push]
GitHub Release: <yes (draft) | yes | no | will ask>
Commits included:
- <subject line>
- <subject line>
… (show up to 20; "and N more…" if longer)
```
Ask the user to approve, adjust the version manually, or cancel. Do not proceed until
explicitly approved.
### 7. Create the annotated tag
Build a tag message:
```text
Release v<new_version>
<bullet list: "- <subject>" for each commit since last tag>
```
Run against the `main` ref explicitly (not `HEAD`, so this works from any branch):
```bash
git tag -a "v<new_version>" main -m "<message>"
```
If the tag already exists locally: stop immediately; never overwrite an existing tag.
### 8. Push the tag (unless `--no-push`)
```bash
git push origin "v<new_version>"
```
Confirm the push succeeded before continuing. If it fails, surface the error verbatim and
stop — do not attempt `--force`.
### 9. GitHub Release (unless `--no-release`)
If `--release` or `--draft` was given, create without prompting.
Otherwise ask: "Create a GitHub Release for v<new_version>? (y/n)"
If creating:
```bash
gh release create "v<new_version>" \
--title "v<new_version>" \
--notes "<same bullet list as step 7>" \
[--draft if --draft was passed]
```
If `gh` is not installed or not authenticated: skip gracefully, print a note, and
suggest `gh auth login`.
### 10. Report
- Tag name and the commit SHA it points to (`git rev-list -n1 "v<new_version>"`).
- Whether the tag was pushed and to which remote.
- GitHub Release URL if one was created.
## Guardrails
- **Never tag without explicit user approval** of the proposed version and commit list.
- **Never tag a dirty working tree** — `git status` must be clean.
- **Never overwrite an existing tag** — if `v<new_version>` already exists locally or
remotely, stop and say so. Use `--pre <suffix>` for iteration.
- **Always `git fetch --tags` first** — stale local tag state produces wrong base versions.
- **Always check CI status on main** before tagging; warn and require confirmation if it
is not green. A tag pointing to broken code is worse than a delayed release.
- **Always tag `main` explicitly** — pass `main` as the ref, not `HEAD`.
- **Never push a tag before it exists locally** and never attempt `--force` on a pushed tag.
- **Only attempt `gh release create` after a successful push** — a release pointing to an
unpushed tag is broken.
- **Pre-release tags** (e.g. `v1.3.0-rc.1`) must not be used as the bump baseline for the
next stable release; note this to the user if relevant.
- If no conventional-commit pattern is found in the commit list, apply a patch bump but
clearly tell the user the inference was a conservative default.
No comments yet. Be the first to comment!