🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

Release Readiness Checker

by @charlie-morrison

Pre-release checklist for shipping software — verify tests pass, changelog updated, version bumped, no debug code, dependencies clean, docs current, no secre...

Versionv1.0.1
Downloads273
Installs1
TERMINAL
clawhub install release-readiness-checker

📖 About This Skill


name: release-readiness-checker description: Pre-release checklist for shipping software — verify tests pass, changelog updated, version bumped, no debug code, dependencies clean, docs current, no secrets committed, CI green. Blocks releases that aren't ready.

Release Readiness Checker

Run a comprehensive pre-release audit before cutting a release. Checks code quality, documentation, dependencies, CI status, and common release blockers. Produces a go/no-go report.

Use when: "are we ready to release", "pre-release check", "release audit", "can we ship this", "release checklist", or before tagging a version.

Commands

1. check — Full Release Readiness Audit

Run all checks and produce a go/no-go verdict.

#### Check 1: Version Bumped

# Check current version
if [ -f "package.json" ]; then
  CURRENT=$(python3 -c "import json; print(json.load(open('package.json')).get('version','none'))" 2>/dev/null)
  echo "Current version: $CURRENT"

# Compare with latest git tag LATEST_TAG=$(git tag --sort=-version:refname 2>/dev/null | head -1) echo "Latest tag: ${LATEST_TAG:-none}"

if [ "$CURRENT" = "${LATEST_TAG#v}" ] || [ "v$CURRENT" = "$LATEST_TAG" ]; then echo "⚠️ Version matches latest tag — did you forget to bump?" fi fi

Check for version in other files

for f in pyproject.toml Cargo.toml setup.py setup.cfg version.txt VERSION; do if [ -f "$f" ]; then grep -i "version" "$f" | head -3 fi done

#### Check 2: Changelog Updated

# Check CHANGELOG exists and has recent entry
for f in CHANGELOG.md CHANGELOG CHANGES.md HISTORY.md; do
  if [ -f "$f" ]; then
    echo "Found: $f"
    # Check if top entry matches current version or is Unreleased
    head -20 "$f"

# Check if there's content under Unreleased UNRELEASED=$(sed -n '/\[Unreleased\]/,/\[/p' "$f" 2>/dev/null | wc -l) if [ "$UNRELEASED" -le 2 ]; then echo "⚠️ Unreleased section appears empty" fi break fi done

If no changelog found

if [ ! -f "CHANGELOG.md" ] && [ ! -f "CHANGELOG" ] && [ ! -f "CHANGES.md" ]; then echo "⚠️ No CHANGELOG file found" fi

#### Check 3: No Debug Code

# Common debug artifacts
echo "=== Debug Code Check ==="
rg -n "console\.log|console\.debug|console\.warn|debugger;" \
  -g '!node_modules' -g '!vendor' -g '!dist' -g '!build' -g '!*.test.*' -g '!*.spec.*' \
  -g '*.{js,ts,jsx,tsx}' --stats 2>&1 | tail -5

rg -n "print\(|breakpoint\(\)|pdb\.set_trace|import pdb|import ipdb" \ -g '!vendor' -g '!dist' -g '*.py' -g '!*test*' --stats 2>&1 | tail -5

rg -n "fmt\.Print|log\.Print" \ -g '*.go' -g '!*_test.go' --stats 2>&1 | tail -5

TODO/FIXME in critical paths (not tests)

CRITICAL_TODOS=$(rg -c "TODO|FIXME|HACK|XXX" \ -g '!node_modules' -g '!vendor' -g '!dist' -g '!*.test.*' -g '!*.spec.*' \ --type-not binary 2>/dev/null | awk -F: '{s+=$2} END {print s+0}') echo "TODO/FIXME count (non-test): $CRITICAL_TODOS"

#### Check 4: Tests Pass

echo "=== Test Check ==="

Detect test runner

if [ -f "package.json" ]; then HAS_TEST=$(python3 -c "import json; d=json.load(open('package.json')); print('yes' if d.get('scripts',{}).get('test','') not in ['','echo \"Error: no test specified\" && exit 1'] else 'no')" 2>/dev/null) if [ "$HAS_TEST" = "yes" ]; then echo "Test command: npm test" echo "(Run 'npm test' to verify — not running automatically to avoid side effects)" else echo "⚠️ No test script configured in package.json" fi fi

if [ -f "pytest.ini" ] || [ -f "setup.cfg" ] || [ -f "pyproject.toml" ]; then if python3 -c "import pytest" 2>/dev/null; then echo "Test runner: pytest detected" fi fi

Check if tests exist at all

TEST_COUNT=$(find . -type f \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" -o -name "*_test.*" \) \ -not -path '*/node_modules/*' -not -path '*/vendor/*' 2>/dev/null | wc -l) echo "Test files found: $TEST_COUNT" if [ "$TEST_COUNT" -eq 0 ]; then echo "❌ No test files found" fi

#### Check 5: Dependencies Clean

echo "=== Dependency Check ==="

Check for outdated (major versions)

if [ -f "package-lock.json" ] || [ -f "yarn.lock" ] || [ -f "pnpm-lock.yaml" ]; then npm outdated 2>/dev/null | head -15 || true

# Check for known vulnerabilities npm audit --json 2>/dev/null | python3 -c " import json, sys try: d = json.load(sys.stdin) vulns = d.get('metadata', {}).get('vulnerabilities', {}) crit = vulns.get('critical', 0) high = vulns.get('high', 0) if crit > 0: print(f'❌ {crit} critical vulnerabilities') elif high > 0: print(f'⚠️ {high} high vulnerabilities') else: print('✅ No critical/high vulnerabilities') except: print('Could not parse npm audit output') " 2>/dev/null fi

Lockfile freshness

if [ -f "package-lock.json" ]; then LOCK_AGE=$(git log -1 --format="%ar" -- package-lock.json 2>/dev/null) echo "Lock file last updated: ${LOCK_AGE:-unknown}" fi

#### Check 6: No Secrets Committed

echo "=== Secrets Check ==="

Common secret patterns

rg -n "(PRIVATE_KEY|SECRET_KEY|API_KEY|ACCESS_TOKEN|password\s*=\s*['\"][^'\"]+['\"])" \ -g '!node_modules' -g '!vendor' -g '!dist' -g '!*.lock' -g '!*.test.*' \ --type-not binary -i 2>/dev/null | \ grep -v "process\.env\|os\.environ\|os\.getenv\|\.env\|example\|sample\|template\|test\|mock\|fake\|dummy" | head -10

Check .env files are gitignored

if [ -f ".env" ]; then if git check-ignore .env >/dev/null 2>&1; then echo "✅ .env is gitignored" else echo "❌ .env is NOT gitignored — potential secret exposure" fi fi

Check for committed .env files

git ls-files '*.env' '.env*' 2>/dev/null | grep -v '.env.example\|.env.sample\|.env.template' | while read f; do echo "⚠️ Committed env file: $f" done

#### Check 7: CI Status

echo "=== CI Check ==="

Check GitHub Actions status for current branch

BRANCH=$(git branch --show-current 2>/dev/null) if command -v gh &>/dev/null; then gh run list --branch "$BRANCH" --limit 3 2>/dev/null || echo "Could not fetch CI status (gh not configured)" else echo "gh CLI not available — check CI manually" fi

Check if CI config exists

for f in .github/workflows/*.yml .github/workflows/*.yaml .gitlab-ci.yml Jenkinsfile .circleci/config.yml; do if ls $f 2>/dev/null | head -1 >/dev/null; then echo "CI config found: $f" fi done 2>/dev/null

#### Check 8: Documentation Current

echo "=== Documentation Check ==="

README exists and is non-trivial

if [ -f "README.md" ]; then LINES=$(wc -l < README.md) echo "README.md: $LINES lines" if [ "$LINES" -lt 10 ]; then echo "⚠️ README.md seems sparse" fi else echo "⚠️ No README.md found" fi

API docs if relevant

for f in docs/ doc/ api-docs/ API.md; do if [ -e "$f" ]; then echo "Docs found: $f" fi done

Check if README references current version

if [ -f "README.md" ] && [ -n "$CURRENT" ]; then if grep -q "$CURRENT" README.md 2>/dev/null; then echo "✅ README references current version ($CURRENT)" fi fi

#### Check 9: Git Status Clean

echo "=== Git Status ==="
UNCOMMITTED=$(git status --porcelain 2>/dev/null | wc -l)
if [ "$UNCOMMITTED" -gt 0 ]; then
  echo "⚠️  $UNCOMMITTED uncommitted changes"
  git status --short 2>/dev/null | head -10
else
  echo "✅ Working tree clean"
fi

Check if current branch is ahead/behind

BRANCH=$(git branch --show-current 2>/dev/null) AHEAD=$(git rev-list --count origin/$BRANCH..$BRANCH 2>/dev/null || echo "?") BEHIND=$(git rev-list --count $BRANCH..origin/$BRANCH 2>/dev/null || echo "?") echo "Branch: $BRANCH (ahead: $AHEAD, behind: $BEHIND)" if [ "$BEHIND" != "0" ] && [ "$BEHIND" != "?" ]; then echo "⚠️ Branch is behind remote — pull before releasing" fi

2. verdict — Go/No-Go Summary

After running all checks, produce a verdict:

# Release Readiness Report
Project: [name] | Version: [version] | Date: [date]

Verdict: 🟢 GO / 🟡 CAUTION / 🔴 NO-GO

Blockers (must fix before release)

  • ❌ 3 critical vulnerabilities in dependencies
  • ❌ .env file committed to repository
  • Warnings (should fix, won't block)

  • ⚠️ Version not bumped from last tag
  • ⚠️ 12 TODO/FIXME comments in source
  • ⚠️ 5 uncommitted changes
  • Passing

  • ✅ Changelog updated
  • ✅ Tests exist (47 test files)
  • ✅ No debug code detected
  • ✅ README current (89 lines)
  • ✅ CI config present
  • Verdict rules:

  • NO-GO: Any critical vulnerability, committed secrets, or no tests
  • CAUTION: Uncommitted changes, outdated deps (major), sparse docs, debug code found
  • GO: All checks pass or only minor warnings
  • 3. checklist — Interactive Checklist

    Generate a markdown checklist for manual review:

    ## Pre-Release Checklist

    Automated (run check to verify)

  • [ ] Version bumped
  • [ ] Changelog updated
  • [ ] No debug code (console.log, debugger, etc.)
  • [ ] Tests pass
  • [ ] Dependencies clean (no critical vulns)
  • [ ] No secrets committed
  • [ ] CI green
  • [ ] Docs updated
  • [ ] Git status clean
  • Manual Review

  • [ ] Breaking changes documented
  • [ ] Migration guide written (if needed)
  • [ ] API deprecation notices sent
  • [ ] Performance regression check
  • [ ] Security review completed
  • [ ] Stakeholders notified
  • [ ] Release notes drafted
  • [ ] Rollback plan documented
  • 4. compare — Compare with Previous Release

    Show what changed since the last tagged release:

    LATEST_TAG=$(git tag --sort=-version:refname 2>/dev/null | head -1)
    if [ -n "$LATEST_TAG" ]; then
      echo "Changes since $LATEST_TAG:"
      echo ""
      echo "=== Commits ==="
      git log "$LATEST_TAG"..HEAD --oneline 2>/dev/null | head -30

    echo "" echo "=== File Stats ===" git diff --stat "$LATEST_TAG"..HEAD 2>/dev/null | tail -5

    echo "" echo "=== New Files ===" git diff --name-only --diff-filter=A "$LATEST_TAG"..HEAD 2>/dev/null | head -20

    echo "" echo "=== Deleted Files ===" git diff --name-only --diff-filter=D "$LATEST_TAG"..HEAD 2>/dev/null | head -20

    echo "" echo "=== Contributors ===" git log "$LATEST_TAG"..HEAD --format="%an" 2>/dev/null | sort -u fi

    Output Formats

  • text (default): Human-readable with status icons
  • json: Machine-readable {version, verdict, blockers: [], warnings: [], passing: [], checks: {name: {status, details}}}
  • markdown: PR/wiki-ready report with tables and checklist
  • CI Integration

    Exit codes:

  • 0: GO (all checks pass)
  • 1: NO-GO (blockers found)
  • 2: CAUTION (warnings only)
  • # Block release on failure
    
  • name: Release readiness check
  • run: | # Agent runs: release-readiness-checker check # Exits 1 if blockers found

    As PR comment

  • name: Post readiness report
  • run: | # Agent runs: release-readiness-checker verdict --format markdown > report.md gh pr comment $PR_NUMBER --body-file report.md

    Notes

  • Does not run tests automatically (to avoid side effects) — reports whether test infrastructure exists and suggests running them
  • Secrets detection uses pattern matching — may have false positives (review flagged items)
  • Git history required for version comparison and branch status
  • CI status check requires gh CLI with authentication
  • Adapts to project type: detects Node.js, Python, Go, Rust from config files
  • Customizable: add .release-readiness.json to adjust thresholds (max TODOs, required docs, etc.)
  • 📋 Tips & Best Practices

  • Does not run tests automatically (to avoid side effects) — reports whether test infrastructure exists and suggests running them
  • Secrets detection uses pattern matching — may have false positives (review flagged items)
  • Git history required for version comparison and branch status
  • CI status check requires gh CLI with authentication
  • Adapts to project type: detects Node.js, Python, Go, Rust from config files
  • Customizable: add .release-readiness.json to adjust thresholds (max TODOs, required docs, etc.)