skills/local/cali-ops-github-releases/SKILL.md
Create GitHub releases following project conventions. Triggers when: user says 'release', 'create release', 'push release', 'deploy to main', 'merge to main', user merges a PR to main, or when git push to main is detected. Also triggers on mentions of: gh release, semver, version bump, changelog, release-please. Covers: config-driven (read .release.yml and execute) and fallback (gh CLI) release flows, versioning rules, tag management, and the mandatory release-on-merge convention.
npx skillsauth add renatocaliari/agent-sync-public-skills cali-ops-github-releasesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Rule: Any merge/push to main MUST include a GitHub release.
Do not wait to be asked — generate release automatically.
Merging to main, pushing to main, creating release manually, setting up automation, hotfix releases, debugging release-please.
| Section | When to consult |
|---------|------------------|
| Release Strategies + sem | Before any release |
| Versioning Rules | Before bumping version |
| Config-Driven (.release.yml) | Project has .release.yml |
| Fallback: Raw gh CLI | No config file |
| Dry-Run / Preview | Preview before executing |
| Examples | First time using skill |
| Edge Cases | Problems arise |
| Test Cases | Activation reference |
Discovery order — always check in this sequence:
.release-please-manifest.json exists → project uses release-please (see references/release-please-flow.md).release.yml exists → use Config-Driven Release belowIf sem is installed (command -v sem), enhance the release with structural analysis.
If not installed, the release proceeds normally using commit-prefix parsing.
Never block a release on sem being absent.
Prefer sem diff over git diff for any entity-level diff (functions, types, methods).
Use git diff only when sem is unavailable — it shows raw lines, not meaningful symbols.
Check: command -v sem && echo "enhanced mode" || echo "commit-parsing mode"
| Enhancement | What sem adds | Fallback |
|-------------|-----------------|----------|
| Bump type detection | Sees signature changes, removed exports → catches breaking changes commit prefixes miss | Parse feat:/fix:/BREAKING: in commit messages |
| Rich changelog | Symbol-level diff (sem diff $LAST_TAG HEAD) alongside commit list | git log --oneline only |
| Pre-release sanity | Confirms actual symbol changes exist before tagging | Trust commit list |
Full commands and integration into Steps 2-3, 10-11, and raw Steps 3-4:
references/sem-enhancement.md
Follow Semantic Versioning 2.0.0:
| Rule | Example |
|------|---------|
| Use alpha/beta only | 0.2.0-alpha, 0.3.1-beta |
| NEVER 1.0.0 without owner confirmation | ❌ 1.0.0 → ✅ 0.9.0 |
| Patch for backwards-compatible fixes | 0.2.0 → 0.2.1 |
| Minor for new features (backwards-compatible) | 0.2.0 → 0.3.0 |
| Major for breaking changes | 0.2.0 → 1.0.0 (with approval) |
Run these BEFORE any release, regardless of which flow is used:
# 1. Check gh CLI is authenticated
gh auth status || { echo "❌ Run: gh auth login"; exit 1; }
# 2. Check clean git state — auto-commit uncommitted changes
if ! git diff --quiet HEAD; then
echo "📝 Uncommitted changes found — auto-committing before release"
git add -A
# Build a descriptive commit message
FILE_COUNT=$(git diff --cached --name-only | wc -l | tr -d ' ')
ADD_DEL=$(git diff --cached --numstat | awk '{a+=$1; d+=$2} END {printf "+%d -%d", a, d}')
if command -v sem &> /dev/null; then
SEM_OUT=$(sem diff 2>/dev/null | head -3 | tr '\n' ' ' | xargs)
fi
if [ -n "$SEM_OUT" ]; then
COMMIT_MSG="chore: ${SEM_OUT} (${FILE_COUNT} files, ${ADD_DEL})"
else
COMMIT_MSG="chore: prepare release (${FILE_COUNT} files, ${ADD_DEL})"
fi
git commit -m "${COMMIT_MSG}"
echo "✅ Changes committed: $(git rev-parse HEAD | head -c 7) — ${COMMIT_MSG}"
fi
# 3. Check we're on main/master (block feature branches)
BRANCH=$(git branch --show-current)
if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
echo "❌ Not on main/master branch. Current: $BRANCH"
echo " git checkout main && git pull # switch to main first"
exit 1
fi
echo "✅ On branch: $BRANCH"
.release.yml)When a project has .release.yml, the skill reads it and executes every step.
No project-level scripts needed — the skill is the executor.
.release.yml format# Suffix appended to version numbers (e.g. 0.11.1-alpha)
# Default: "" (no suffix)
suffix: alpha
# Version files to sync when bumping
# The skill will update the version field in each file.
# Default: [] (auto-detect package.json, pyproject.toml, Cargo.toml)
version_files:
- package.json
# Command to run tests before release
# Default: "" (no tests)
test_command: npm test
# Command to run lint/typecheck before release
# Default: "" (no lint)
lint_command: npm run typecheck
The skill follows this exact flow:
Step 0: Read .release.yml
Step 1: Run Pre-Flight Checklist (gh auth + clean git)
Step 2: Detect last tag (git describe --tags)
Step 3: Parse version and bump it (preserving suffix)
Step 4: Confirm major release if applicable
Step 5: Run test_command (if defined)
Step 6: Run lint_command (if defined)
Step 7: Sync version in all version_files
Step 8: Create annotated git tag
Step 9: Push tag to GitHub
Step 10: Build changelog from commits since last tag
Step 11: Create GitHub Release (gh release create)
Step 12: Verify and report
Step 0 — Read config:
# Read .release.yml values. Defaults if missing:
SUFFIX="" # default: no suffix
VERSION_FILES="" # auto-detect
TEST_CMD=""
LINT_CMD=""
Step 2-3 — Detect & bump version:
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
RAW="${LAST_TAG#v}"
# Parse: major.minor.patch (suffix comes from .release.yml, ignore tag suffix)
IFS='-' read -r VERSION_PART _REST <<< "$RAW"
IFS='.' read -r MAJOR MINOR PATCH <<< "${VERSION_PART}"
# Bump — TYPE is patch/minor/major determined from commits
case "$TYPE" in
patch) PATCH=$((PATCH + 1)) ;;
minor) MINOR=$((MINOR + 1)); PATCH=0 ;;
major) MAJOR=$((MAJOR + 1)); MINOR=0; PATCH=0 ;;
esac
# Apply suffix from .release.yml (populated by agent reading the config)
CONFIG_SUFFIX="${YML_SUFFIX:-}"
if [ -n "$CONFIG_SUFFIX" ]; then
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}-${CONFIG_SUFFIX}"
else
NEW_VERSION="${MAJOR}.${MINOR}.${PATCH}"
fi
NEW_TAG="v${NEW_VERSION}"
Step 4 — Major confirmation:
if [ "$TYPE" = "major" ]; then
echo "⚠ MAJOR release: $LAST_TAG → $NEW_TAG"
echo "This requires owner confirmation. Ask the user before proceeding."
fi
Step 5-6 — Run tests/lint:
if [ -n "$TEST_CMD" ]; then
echo "➜ Running tests: $TEST_CMD"
eval "$TEST_CMD" || { echo "Tests failed — aborting release"; exit 1; }
fi
if [ -n "$LINT_CMD" ]; then
echo "➜ Running lint: $LINT_CMD"
eval "$LINT_CMD" || { echo "Lint failed — aborting release"; exit 1; }
fi
Step 5b — Signoff gate (CI fallback): If the repo has no GitHub Actions CI, sign the release commit cryptographically.
if [ -n "$TEST_CMD" ] && ! ls .github/workflows/ 2>/dev/null | grep -q .; then
echo "➜ No GitHub Actions CI detected — running gh signoff..."
gh signoff || { echo "Signoff failed — aborting release"; exit 1; }
fi
Requires gh-signoff: gh extension install basecamp/gh-signoff
Step 7 — Sync version files:
# Read version_files from .release.yml
# For each file, update the version field
for f in "${VERSION_FILES[@]}"; do
case "$f" in
*.json)
node -e "
const fs = require('fs');
const p = JSON.parse(fs.readFileSync('./$f', 'utf8'));
if (p.version) {
p.version = '$NEW_VERSION';
fs.writeFileSync('./$f', JSON.stringify(p, null, 2) + '\n');
} else {
console.error('WARNING: no version field in $f');
}
"
git add "$f"
;;
*.toml)
sed -i.bak 's/^version = ".*"/version = "'"$NEW_VERSION"'"/' "$f" && rm -f "$f.bak"
git add "$f"
;;
*.go)
# Go version file: var/const Version = "x.y.z"
sed -i.bak 's/\(Version\\s*=\\s*\)".*"/\1"'"$NEW_VERSION"'"/' "$f" && rm -f "$f.bak"
git add "$f"
;;
esac
done
# Commit version bump BEFORE tagging (tag must point to the bumped commit)
if ! git diff --cached --quiet; then
git commit -m "chore: bump version to $NEW_VERSION"
fi
Step 8-9 — Tag + push:
git tag -a "$NEW_TAG" -m "Release $NEW_TAG"
git push origin "$NEW_TAG"
Step 10-11 — Create GitHub Release:
Follow Keep a Changelog 1.1.0 format:
# Build changelog — handle root commit / first tag gracefully
PREV_TAG=$(git describe --tags --abbrev=0 "$NEW_TAG"^ 2>/dev/null || true)
CATEGORIZED=$(mktemp)
CHANGELOG=$(mktemp)
# Categorize commits by conventional commit prefix
if [ -n "$PREV_TAG" ]; then
LOG_RANGE="$PREV_TAG..$NEW_TAG"
SCOPE_LABEL="$PREV_TAG"
else
LOG_RANGE="HEAD"
SCOPE_LABEL="initial commit"
fi
# Build categorized changelog following Keep a Changelog format
{
echo "# Release $NEW_TAG"
echo ""
echo "## What's Changed"
echo ""
# Added (feat:)
ADDED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^feat" 2>/dev/null | sed 's/^/* /')
if [ -n "$ADDED" ]; then
echo "### Added"
echo "$ADDED"
echo ""
fi
# Fixed (fix:)
FIXED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^fix" 2>/dev/null | sed 's/^/* /')
if [ -n "$FIXED" ]; then
echo "### Fixed"
echo "$FIXED"
echo ""
fi
# Changed (refactor:, perf:, chore:)
CHANGED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^refactor\|^perf\|^chore" 2>/dev/null | sed 's/^/* /')
if [ -n "$CHANGED" ]; then
echo "### Changed"
echo "$CHANGED"
echo ""
fi
# Security (security:)
SECURITY=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^security\|^sec" 2>/dev/null | sed 's/^/* /')
if [ -n "$SECURITY" ]; then
echo "### Security"
echo "$SECURITY"
echo ""
fi
# Remaining uncategorized commits
CATEGORIZED_COM=$( (git log "$LOG_RANGE" --oneline --no-decorate --grep="^feat\|^fix\|^refactor\|^perf\|^chore\|^security\|^sec" 2>/dev/null) | wc -l | tr -d ' ')
TOTAL_COM=$(git log "$LOG_RANGE" --oneline --no-decorate 2>/dev/null | wc -l | tr -d ' ')
if [ "$TOTAL_COM" -gt "$CATEGORIZED_COM" ]; then
echo "### Other"
git log "$LOG_RANGE" --oneline --no-decorate --invert-grep --grep="^feat\|^fix\|^refactor\|^perf\|^chore\|^security\|^sec" 2>/dev/null | sed 's/^/* /'
echo ""
fi
echo "---"
echo "_Full diff: [$SCOPE_LABEL...$NEW_TAG](https://github.com/$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo 'repo')/compare/$SCOPE_LABEL...$NEW_TAG)_"
} > "$CATEGORIZED"
# Use --notes-file to avoid shell escaping bugs
gh release create "$NEW_TAG" --title "$NEW_TAG" --notes-file "$CATEGORIZED"
rm -f "$CATEGORIZED" "$CHANGELOG"
Use this when the project has neither .release-please-manifest.json nor .release.yml.
# Execute the checks in [Pre-Flight Checklist](#pre-flight-checklist-all-flows)
# If any check fails, stop and tell the user what to fix.
If the repo has no GitHub Actions CI, run tests and sign the release.
TEST_CMD=""
[ -f package.json ] && TEST_CMD="npm test"
[ -f go.mod ] && TEST_CMD="go test ./..."
[ -f Cargo.toml ] && TEST_CMD="cargo test"
[ -f pyproject.toml ] && TEST_CMD="pytest"
if [ -n "$TEST_CMD" ] && ! ls .github/workflows/ 2>/dev/null | grep -q .; then
echo "➜ No GitHub Actions CI detected — running tests..."
eval "$TEST_CMD" || { echo "Tests failed — aborting release"; exit 1; }
echo "➜ Running gh signoff..."
gh signoff || { echo "Signoff failed — aborting release"; exit 1; }
fi
Requires gh-signoff: gh extension install basecamp/gh-signoff
# Find last tag
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -z "$LAST_TAG" ]; then
echo "No previous tags found. This is the first release."
# Check for existing version indicators
cat package.json 2>/dev/null | grep '"version"' || true
grep 'version =' Cargo.toml 2>/dev/null || true
else
echo "Last tag: $LAST_TAG"
echo ""
echo "Commits since last release:"
git log "$LAST_TAG"..HEAD --oneline --no-decorate
fi
Based on the commits found:
fix: commits → patch bump
feat: commits → minor bump
BREAKING CHANGE: or !: → major bump (requires user confirmation)
refactor: / chore: / docs: → patch bump (safe default)
Mixed feat + fix → minor bump
No conventional commit prefixes → ask the user with this template:
"The commits don't follow conventional commit format (no
feat:/fix:prefixes). Here are the changes since the last release:[paste git log output]What kind of version bump should this be?
patch(fixes) /minor(features) /major(breaking)"
# Compute next version
RAW="${LAST_TAG#v}"
IFS='.' read -r MAJOR MINOR PATCH <<< "$RAW"
# Bump based on decision above
NEW_TAG="v${MAJOR}.${MINOR}.$((PATCH + 1))" # default: patch
echo "Proposed: $LAST_TAG → $NEW_TAG"
# Build categorized changelog following Keep a Changelog format
CHANGELOG=$(mktemp)
if [ -n "$LAST_TAG" ]; then
LOG_RANGE="$LAST_TAG..HEAD"
else
LOG_RANGE="HEAD"
fi
{
echo "# Release $NEW_TAG"
echo ""
echo "## What's Changed"
echo ""
# Added (feat:)
ADDED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^feat" 2>/dev/null | sed 's/^/* /')
if [ -n "$ADDED" ]; then
echo "### Added"
echo "$ADDED"
echo ""
fi
# Fixed (fix:)
FIXED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^fix" 2>/dev/null | sed 's/^/* /')
if [ -n "$FIXED" ]; then
echo "### Fixed"
echo "$FIXED"
echo ""
fi
# Changed (refactor:, perf:, chore:)
CHANGED=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^refactor\|^perf\|^chore" 2>/dev/null | sed 's/^/* /')
if [ -n "$CHANGED" ]; then
echo "### Changed"
echo "$CHANGED"
echo ""
fi
# Security (security:)
SECURITY=$(git log "$LOG_RANGE" --oneline --no-decorate --grep="^security\|^sec" 2>/dev/null | sed 's/^/* /')
if [ -n "$SECURITY" ]; then
echo "### Security"
echo "$SECURITY"
echo ""
fi
# Remaining uncategorized commits
CATEGORIZED_COM=$( (git log "$LOG_RANGE" --oneline --no-decorate --grep="^feat\|^fix\|^refactor\|^perf\|^chore\|^security\|^sec" 2>/dev/null) | wc -l | tr -d ' ')
TOTAL_COM=$(git log "$LOG_RANGE" --oneline --no-decorate 2>/dev/null | wc -l | tr -d ' ')
if [ "$TOTAL_COM" -gt "$CATEGORIZED_COM" ]; then
echo "### Other"
git log "$LOG_RANGE" --oneline --no-decorate --invert-grep --grep="^feat\|^fix\|^refactor\|^perf\|^chore\|^security\|^sec" 2>/dev/null | sed 's/^/* /'
echo ""
fi
} > "$CHANGELOG"
# Create the release
gh release create "$NEW_TAG" \
--title "$NEW_TAG" \
--notes-file "$CHANGELOG"
rm -f "$CHANGELOG"
gh release view "$NEW_TAG"
echo "✅ Release $NEW_TAG created"
Input: "Just merged the auth feature to main"
Discovery: .release.yml exists → use config-driven flow
Execution:
.release.yml: suffix=alpha, test_command=npm test, version_files=[package.json]v0.2.0-alphav0.3.0-alpha (feature = minor)npm test ✅package.json → 0.3.0-alphav0.3.0-alphaOutput: "Released v0.3.0-alpha. Tests passed. Version synced to package.json."
Input: "Release this"
Discovery: No .release.yml, no release-please → fallback to raw gh CLI
Execution:
v0.1.0feat: add login, fix: button colorv0.2.0gh release create v0.2.0 --title "v0.2.0" --notes "..."Output: "Released v0.2.0"
Input: "Production is down, critical bug in auth"
Execution:
git checkout -b hotfix/fix-auth-crash main.release.yml exists, raw gh CLI otherwise)Output: "Hotfix released. Production should recover."
To preview what would happen without making changes, the agent should:
sem available, run sem diff to enhance the previewWould create tag vX.Y.Z from branch <branch> with N commitssem diff if available)Example output:
[Dry-Run] sem: 3 symbols changed (1 new function, 2 modified)
[Dry-Run] Would bump v0.2.0-alpha → v0.3.0-alpha (minor: 2 feat commits)
[Dry-Run] Changelog:
feat: add login page
feat: add dashboard
fix: button hover color
[Dry-Run] No actions taken. Run without --dry-run to execute.
go test ./... or npm testgit diff v0.X.Y-alpha..HEAD --statgh auth statusgh auth logingit tag -l "v0.X.Y*"sem is installed: chore: <sem diff summary> (N files, +M -K)chore: prepare release (N files, +M -K)git reset --soft HEAD~1git tag -s instead of -a)tag.gpgSign = true in git config, use -s flagerror: gpg failed to sign the taggit push origin "$NEW_TAG" is rejected: "Tag push rejected — main branch may be protected"mainpackages/*), .release.yml needs version_files per packagepkg-a-v0.3.0, pkg-b-v0.1.5)feat:, fix:, etc.)refactor: commits don't trigger releases — use manual flow.release-please-manifest.json existsreferences/release-please-flow.mdError reading JToken from JsonReader or syntax errorsjqreferences/release-please-flow.mdv0.1.0 (or v0.1.0-alpha with suffix)references/release-please-flow.md — release-please automation, refactor handling, JSON safety, configreferences/sem-enhancement.md — sem diff: bump detection, rich changelog, sanity checkcali-ops-deploy-github-tailscale — deploy pipeline (trigger after release)tools
Extrai métricas estruturadas, cálculos e estimativas de transcripts de entrevistas com clientes do Sommelier de IA. Produz um JSON com dores, frequências, tempo gasto, pessoas envolvidas, economia potencial, ROI e recomendações financeiras. Projetado para alimentar o cali-degustia-diagnostico ou integrar com dashboards/planilhas.
tools
Guia a coleta de depoimentos de clientes do Sommelier de IA no momento certo do processo, usando a abordagem de Hormozi: pedir depois da primeira evidência de resultado, nunca na entrega. Gera depoimentos mais autênticos e reduz a sensação de que o cliente está sendo "solicitado".
development
[stelow] Full UX critique for visual interfaces. Accepts a live URL, source code directory, or screenshot image. Evaluates accessibility (WCAG AA), Nielsen's 10 heuristics, visual hierarchy, cognitive load, consistency, mobile responsiveness, AI slop, emotional journey, and design personas — then generates a classified gap report. Standalone or integrated into stelow and stelow-product-testing-execution.
development
Building trust through perception and guarantee mechanisms. Covers ten pillars to materialize trust, guarantee types from unconditional to anti-guarantees, and strategic approaches for different contexts.