diff --git a/AGENTS.md b/AGENTS.md
index d18eacc..9a10010 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -9,7 +9,7 @@ This is a Bash CLI project. The active runtime is the root `devloop` executable.
 - `bash tests/devloop_test.sh`: run the shell test suite.
 - `./install.sh`: link `devloop` into `~/.local/bin` or `DEVLOOP_BIN_DIR`.
 - `./devloop --plain .specs/change.md`: example local CLI invocation from a target git worktree.
-- `./release.sh 0.1.0 --dry-run`: validate the release path without changing files.
+- `./release.sh patch --dry-run`: validate the release path without changing files.
 
 ## Coding Style & Naming Conventions
 
diff --git a/README.md b/README.md
index d9e3a58..3e081c7 100644
--- a/README.md
+++ b/README.md
@@ -164,14 +164,14 @@ Release notes in [`CHANGELOG.md`](CHANGELOG.md) are generated from Conventional
 brew install git-cliff
 ```
 
-Cut a release from a clean tree:
+Cut a release from a clean tree by choosing the bump:
 
 ```sh
-./release.sh 0.1.0 --dry-run
-./release.sh 0.1.0
+./release.sh patch --dry-run
+./release.sh patch
 ```
 
-That updates `VERSION` and [`CHANGELOG.md`](CHANGELOG.md), runs `bash tests/devloop_test.sh`, commits `chore: release 0.1.0`, and creates an annotated `v0.1.0` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`.
+Use `patch`, `minor`, or `major`. The script reads the current [`VERSION`](VERSION), computes the next SemVer version, updates `VERSION` and [`CHANGELOG.md`](CHANGELOG.md), runs `bash tests/devloop_test.sh`, commits `chore: release <version>`, and creates an annotated `v<version>` tag. Add `--push` to push the release branch and tag. By default, pushed releases must run from `main`.
 
 ## License
 
diff --git a/release.sh b/release.sh
index f54c256..976874d 100755
--- a/release.sh
+++ b/release.sh
@@ -15,14 +15,14 @@ ROOT="$(cd -P "$(dirname "$SCRIPT_PATH")" >/dev/null 2>&1 && pwd)"
 
 release_usage() {
   cat <<'EOF'
-usage: ./release.sh <version> [--dry-run] [--push]
+usage: ./release.sh <patch|minor|major> [--dry-run] [--push]
 
-Creates a release commit and annotated tag from a SemVer version.
+Bumps VERSION, creates a release commit, and creates an annotated tag.
 
 Examples:
-  ./release.sh 0.1.0 --dry-run
-  ./release.sh 0.1.0
-  ./release.sh 0.1.0 --push
+  ./release.sh patch --dry-run
+  ./release.sh minor
+  ./release.sh major --push
 EOF
 }
 
@@ -35,6 +35,52 @@ release_tag_for_version() {
   printf 'v%s\n' "$1"
 }
 
+release_current_version() {
+  sed -n '1p' "$ROOT/VERSION" 2>/dev/null || true
+}
+
+release_next_version() {
+  local bump="$1"
+  local current="$2"
+  local major minor patch
+
+  if ! release_version_valid "$current"; then
+    printf 'invalid current VERSION: %s\n' "$current" >&2
+    return 2
+  fi
+
+  case "$current" in
+    *-*|*+*)
+      printf 'cannot bump prerelease/build VERSION: %s\n' "$current" >&2
+      return 2
+      ;;
+  esac
+
+  IFS=. read -r major minor patch <<EOF
+$current
+EOF
+  case "$bump" in
+    major)
+      major=$((major + 1))
+      minor=0
+      patch=0
+      ;;
+    minor)
+      minor=$((minor + 1))
+      patch=0
+      ;;
+    patch)
+      patch=$((patch + 1))
+      ;;
+    *)
+      printf 'unknown bump: %s\n' "$bump" >&2
+      return 2
+      ;;
+  esac
+
+  printf '%s.%s.%s\n' "$major" "$minor" "$patch"
+}
+
 release_require_command() {
   local command="$1"
   if command -v "$command" >/dev/null 2>&1; then return 0; fi
@@ -74,7 +120,8 @@ release_assert_push_branch() {
 }
 
 release_main() {
-  local version=""
+  local bump=""
+  local current version
   local dry_run=false
   local push=false
   local tag branch
@@ -90,16 +137,31 @@ release_main() {
         return 2
         ;;
       *)
-        if [ -n "$version" ]; then
+        if [ -n "$bump" ]; then
           release_usage >&2
           return 2
         fi
-        version="$1"
+        bump="$1"
         ;;
     esac
     shift
   done
 
+  case "$bump" in
+    patch|minor|major) ;;
+    "")
+      release_usage >&2
+      return 2
+      ;;
+    *)
+      printf 'invalid bump: %s\n' "$bump" >&2
+      release_usage >&2
+      return 2
+      ;;
+  esac
+
+  current="$(release_current_version)"
+  version="$(release_next_version "$bump" "$current")" || return $?
   if [ -z "$version" ]; then
     release_usage >&2
     return 2
@@ -113,7 +175,8 @@ release_main() {
   release_require_command git
   if [ "$dry_run" = true ]; then
     release_assert_tag_available "$tag"
-    printf 'release: %s\n' "$tag"
+    printf 'current: %s\n' "$current"
+    printf 'next: %s (%s)\n' "$version" "$tag"
     if [ -n "$(git -C "$ROOT" status --porcelain)" ]; then
       printf '%s\n' "note: actual release requires a clean working tree"
     fi
diff --git a/tests/devloop_test.sh b/tests/devloop_test.sh
index 92d64b8..eb67a09 100755
--- a/tests/devloop_test.sh
+++ b/tests/devloop_test.sh
@@ -305,12 +305,21 @@ ok "pure helpers"
   if release_version_valid "1.2"; then fail "release version accepted missing patch"; fi
   if release_version_valid "1.2.3-alpha.01"; then fail "release version accepted leading zero prerelease"; fi
   equals "$(release_tag_for_version "1.2.3")" "v1.2.3" "release tag"
+  equals "$(release_next_version patch "0.1.0")" "0.1.1" "patch bump"
+  equals "$(release_next_version minor "0.1.0")" "0.2.0" "minor bump"
+  equals "$(release_next_version major "0.1.0")" "1.0.0" "major bump"
+  if release_next_version patch "0.1.0-alpha.1" >/dev/null 2>&1; then fail "release bump accepted prerelease"; fi
   release_require_command() {
     if [ "$1" = "git-cliff" ]; then return 1; fi
     return 0
   }
-  dry_run_output="$(release_main "9.9.9" --dry-run)" || fail "release dry-run required git-cliff"
-  contains "$dry_run_output" "would tag: v9.9.9" "release dry-run"
+  ROOT="$work/release-root"
+  mkdir -p "$ROOT"
+  git init -q "$ROOT"
+  printf '%s\n' "9.9.9" > "$ROOT/VERSION"
+  dry_run_output="$(release_main "patch" --dry-run)" || fail "release dry-run required git-cliff"
+  contains "$dry_run_output" "next: 9.9.10 (v9.9.10)" "release dry-run"
+  contains "$dry_run_output" "would tag: v9.9.10" "release dry-run"
 )
 ok "release helpers"
 
