diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index dbcfed29..b6c7949d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -9,7 +9,7 @@ { "name": "superpowers", "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.6", + "version": "5.0.7", "source": "./", "author": { "name": "Jesse Vincent", diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 1a2018bc..cad4d55f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "superpowers", "description": "Core skills library for Claude Code: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.6", + "version": "5.0.7", "author": { "name": "Jesse Vincent", "email": "jesse@fsck.com" diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 24466b35..153deac4 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "superpowers", "displayName": "Superpowers", "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.6", + "version": "5.0.7", "author": { "name": "Jesse Vincent", "email": "jesse@fsck.com" diff --git a/.version-bump.json b/.version-bump.json new file mode 100644 index 00000000..f5dbe315 --- /dev/null +++ b/.version-bump.json @@ -0,0 +1,19 @@ +{ + "files": [ + { "path": "package.json", "field": "version" }, + { "path": ".claude-plugin/plugin.json", "field": "version" }, + { "path": ".cursor-plugin/plugin.json", "field": "version" }, + { "path": ".claude-plugin/marketplace.json", "field": "plugins.0.version" }, + { "path": "gemini-extension.json", "field": "version" } + ], + "audit": { + "exclude": [ + "CHANGELOG.md", + "RELEASE-NOTES.md", + "node_modules", + ".git", + ".version-bump.json", + "scripts/bump-version.sh" + ] + } +} diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 093863e6..675eca2a 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -1,6 +1,6 @@ # Superpowers Release Notes -## Unreleased +## v5.0.7 (2026-03-31) ### GitHub Copilot CLI Support diff --git a/gemini-extension.json b/gemini-extension.json index 2042d13c..d52654ae 100644 --- a/gemini-extension.json +++ b/gemini-extension.json @@ -1,6 +1,6 @@ { "name": "superpowers", "description": "Core skills library: TDD, debugging, collaboration patterns, and proven techniques", - "version": "5.0.6", + "version": "5.0.7", "contextFileName": "GEMINI.md" } diff --git a/package.json b/package.json index 980800ad..04f22b42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "superpowers", - "version": "5.0.6", + "version": "5.0.7", "type": "module", "main": ".opencode/plugins/superpowers.js" } diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 00000000..01adec99 --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +# +# bump-version.sh — bump version numbers across all declared files, +# with drift detection and repo-wide audit for missed files. +# +# Usage: +# bump-version.sh Bump all declared files to new version +# bump-version.sh --check Report current versions (detect drift) +# bump-version.sh --audit Check + grep repo for old version strings +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +CONFIG="$REPO_ROOT/.version-bump.json" + +if [[ ! -f "$CONFIG" ]]; then + echo "error: .version-bump.json not found at $CONFIG" >&2 + exit 1 +fi + +# --- helpers --- + +# Read a dotted field path from a JSON file. +# Handles both simple ("version") and nested ("plugins.0.version") paths. +read_json_field() { + local file="$1" field="$2" + # Convert dot-path to jq path: "plugins.0.version" -> .plugins[0].version + local jq_path + jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g') + jq -r "$jq_path" "$file" +} + +# Write a dotted field path in a JSON file, preserving formatting. +write_json_field() { + local file="$1" field="$2" value="$3" + local jq_path + jq_path=$(echo "$field" | sed -E 's/\.([0-9]+)/[\1]/g' | sed 's/^/./' | sed 's/\.\././g') + local tmp="${file}.tmp" + jq "$jq_path = \"$value\"" "$file" > "$tmp" && mv "$tmp" "$file" +} + +# Read the list of declared files from config. +# Outputs lines of "pathfield" +declared_files() { + jq -r '.files[] | "\(.path)\t\(.field)"' "$CONFIG" +} + +# Read the audit exclude patterns from config. +audit_excludes() { + jq -r '.audit.exclude[]' "$CONFIG" 2>/dev/null +} + +# --- commands --- + +cmd_check() { + local has_drift=0 + local versions=() + + echo "Version check:" + echo "" + + while IFS=$'\t' read -r path field; do + local fullpath="$REPO_ROOT/$path" + if [[ ! -f "$fullpath" ]]; then + printf " %-45s MISSING\n" "$path ($field)" + has_drift=1 + continue + fi + local ver + ver=$(read_json_field "$fullpath" "$field") + printf " %-45s %s\n" "$path ($field)" "$ver" + versions+=("$ver") + done < <(declared_files) + + echo "" + + # Check if all versions match + local unique + unique=$(printf '%s\n' "${versions[@]}" | sort -u | wc -l | tr -d ' ') + if [[ "$unique" -gt 1 ]]; then + echo "DRIFT DETECTED — versions are not in sync:" + printf '%s\n' "${versions[@]}" | sort | uniq -c | sort -rn | while read -r count ver; do + echo " $ver ($count files)" + done + has_drift=1 + else + echo "All declared files are in sync at ${versions[0]}" + fi + + return $has_drift +} + +cmd_audit() { + # First run check + cmd_check || true + echo "" + + # Determine the current version (most common across declared files) + local current_version + current_version=$( + while IFS=$'\t' read -r path field; do + local fullpath="$REPO_ROOT/$path" + [[ -f "$fullpath" ]] && read_json_field "$fullpath" "$field" + done < <(declared_files) | sort | uniq -c | sort -rn | head -1 | awk '{print $2}' + ) + + if [[ -z "$current_version" ]]; then + echo "error: could not determine current version" >&2 + return 1 + fi + + echo "Audit: scanning repo for version string '$current_version'..." + echo "" + + # Build grep exclude args + local -a exclude_args=() + while IFS= read -r pattern; do + exclude_args+=("--exclude=$pattern" "--exclude-dir=$pattern") + done < <(audit_excludes) + + # Also always exclude binary files and .git + exclude_args+=("--exclude-dir=.git" "--exclude-dir=node_modules" "--binary-files=without-match") + + # Get list of declared paths for comparison + local -a declared_paths=() + while IFS=$'\t' read -r path _field; do + declared_paths+=("$path") + done < <(declared_files) + + # Grep for the version string + local found_undeclared=0 + while IFS= read -r match; do + local match_file + match_file=$(echo "$match" | cut -d: -f1) + # Make path relative to repo root + local rel_path="${match_file#$REPO_ROOT/}" + + # Check if this file is in the declared list + local is_declared=0 + for dp in "${declared_paths[@]}"; do + if [[ "$rel_path" == "$dp" ]]; then + is_declared=1 + break + fi + done + + if [[ "$is_declared" -eq 0 ]]; then + if [[ "$found_undeclared" -eq 0 ]]; then + echo "UNDECLARED files containing '$current_version':" + found_undeclared=1 + fi + echo " $match" + fi + done < <(grep -rn "${exclude_args[@]}" -F "$current_version" "$REPO_ROOT" 2>/dev/null || true) + + if [[ "$found_undeclared" -eq 0 ]]; then + echo "No undeclared files contain the version string. All clear." + else + echo "" + echo "Review the above files — if they should be bumped, add them to .version-bump.json" + echo "If they should be skipped, add them to the audit.exclude list." + fi +} + +cmd_bump() { + local new_version="$1" + + # Validate semver-ish format + if ! echo "$new_version" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+'; then + echo "error: '$new_version' doesn't look like a version (expected X.Y.Z)" >&2 + exit 1 + fi + + echo "Bumping all declared files to $new_version..." + echo "" + + while IFS=$'\t' read -r path field; do + local fullpath="$REPO_ROOT/$path" + if [[ ! -f "$fullpath" ]]; then + echo " SKIP (missing): $path" + continue + fi + local old_ver + old_ver=$(read_json_field "$fullpath" "$field") + write_json_field "$fullpath" "$field" "$new_version" + printf " %-45s %s -> %s\n" "$path ($field)" "$old_ver" "$new_version" + done < <(declared_files) + + echo "" + echo "Done. Running audit to check for missed files..." + echo "" + cmd_audit +} + +# --- main --- + +case "${1:-}" in + --check) + cmd_check + ;; + --audit) + cmd_audit + ;; + --help|-h|"") + echo "Usage: bump-version.sh | --check | --audit" + echo "" + echo " Bump all declared files to the given version" + echo " --check Show current versions, detect drift" + echo " --audit Check + scan repo for undeclared version references" + exit 0 + ;; + --*) + echo "error: unknown flag '$1'" >&2 + exit 1 + ;; + *) + cmd_bump "$1" + ;; +esac