From 8cb128af177d33b22bcd3f93c0906493a4774cfb Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 20:11:39 +0100 Subject: [PATCH 01/12] Show dirty/merged for worktree --- lib/bonchi/cli.rb | 34 +++++++++++++++++++++++++++++++++- lib/bonchi/colors.rb | 6 ++++-- lib/bonchi/git.rb | 8 ++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index ba37f18..4d5261f 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -2,6 +2,8 @@ module Bonchi class CLI < Thor + include Colors + def self.exit_on_failure? true end @@ -124,7 +126,37 @@ def setup(*args) desc "list", "List all worktrees" def list - Git.worktree_list.each { |line| puts line } + lines = Git.worktree_list + base = Git.default_base_branch + home = Dir.home + + lines.each do |line| + branch = line[/\[([^\]]+)\]/, 1] + path = line.split(/\s+/).first + line = line.sub(home, "~") + + unless branch + puts line + next + end + + if branch == base + puts line + next + end + + merged = Git.merged?(branch, into: base) + clean = Git.clean?(path) + tags = [] + tags << "#{color(:yellow)}dirty#{reset}" unless clean + tags << "#{color(:green)}merged#{reset}" if merged + + if tags.any? + puts "#{line} #{tags.join(" ")}" + else + puts line + end + end end desc "remove BRANCH", "Remove a worktree" diff --git a/lib/bonchi/colors.rb b/lib/bonchi/colors.rb index 5f99f1b..0a7d49a 100644 --- a/lib/bonchi/colors.rb +++ b/lib/bonchi/colors.rb @@ -3,16 +3,18 @@ module Colors private def color(name) - return "" if ENV.key?("NO_COLOR") + return "" if ENV.key?("NO_COLOR") || !$stdout.tty? case name when :red then "\e[31m" + when :green then "\e[32m" when :yellow then "\e[33m" + when :dim then "\e[2m" end end def reset - return "" if ENV.key?("NO_COLOR") + return "" if ENV.key?("NO_COLOR") || !$stdout.tty? "\e[0m" end diff --git a/lib/bonchi/git.rb b/lib/bonchi/git.rb index f13186d..caebe79 100644 --- a/lib/bonchi/git.rb +++ b/lib/bonchi/git.rb @@ -61,6 +61,14 @@ def worktree_prune system("git", "worktree", "prune") end + def clean?(worktree) + `git -C #{worktree.shellescape} status --porcelain`.strip.empty? + end + + def merged?(branch, into: default_base_branch) + system("git", "merge-base", "--is-ancestor", branch, into) + end + def fetch_pr(pr_number) system("git", "fetch", "origin", "pull/#{pr_number}/head:pr-#{pr_number}") end From 0f2c1892cd1f13fd34e0d24712ea43c8398f47d8 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 20:41:19 +0100 Subject: [PATCH 02/12] Update command docs ls and rm --- lib/bonchi/cli.rb | 15 +++++++++++++++ lib/bonchi/git.rb | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index 4d5261f..f17d76f 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -125,6 +125,12 @@ def setup(*args) end desc "list", "List all worktrees" + long_desc <<~DESC + List all worktrees. Non-main branches are annotated with: + + \x5 dirty — has uncommitted changes or untracked files + \x5 merged — branch has been merged into the default branch + DESC def list lines = Git.worktree_list base = Git.default_base_branch @@ -164,6 +170,9 @@ def list Remove a worktree and its directory. Refuses to remove worktrees with uncommitted changes or untracked files unless --force is used. + If the branch has been merged into the default branch, it is + automatically deleted. Unmerged branches are kept. + Aliases: rm DESC option :force, type: :boolean, default: false, desc: "Force removal even with uncommitted changes" @@ -173,6 +182,12 @@ def remove(branch) Git.worktree_remove(path, force: options[:force]) puts "Removed worktree: #{path}" + + if Git.merged?(branch) + Git.delete_branch(branch) + puts "Deleted merged branch: #{branch}" + end + signal_cd(Git.main_worktree) end diff --git a/lib/bonchi/git.rb b/lib/bonchi/git.rb index caebe79..79835d3 100644 --- a/lib/bonchi/git.rb +++ b/lib/bonchi/git.rb @@ -69,6 +69,10 @@ def merged?(branch, into: default_base_branch) system("git", "merge-base", "--is-ancestor", branch, into) end + def delete_branch(branch) + system("git", "branch", "-d", branch) || abort("Failed to delete branch: #{branch}") + end + def fetch_pr(pr_number) system("git", "fetch", "origin", "pull/#{pr_number}/head:pr-#{pr_number}") end From 523687cf9d534679a93da67246467ba0651aabfc Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 20:48:37 +0100 Subject: [PATCH 03/12] Add rmf and rmrf --- lib/bonchi/cli.rb | 44 ++++++++++++++++++++++++++++++++------------ lib/bonchi/git.rb | 5 +++-- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index f17d76f..2d046ec 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -165,7 +165,7 @@ def list end end - desc "remove BRANCH", "Remove a worktree" + desc "remove BRANCH", "Remove a worktree (and merged branch)" long_desc <<~DESC Remove a worktree and its directory. Refuses to remove worktrees with uncommitted changes or untracked files unless --force is used. @@ -173,22 +173,21 @@ def list If the branch has been merged into the default branch, it is automatically deleted. Unmerged branches are kept. - Aliases: rm + Aliases: rm, rmf (force), rmrf (force + delete unmerged branch) DESC option :force, type: :boolean, default: false, desc: "Force removal even with uncommitted changes" def remove(branch) - path = Git.worktree_path_for(branch) - abort "Error: No worktree found for branch: #{branch}" unless path - - Git.worktree_remove(path, force: options[:force]) - puts "Removed worktree: #{path}" + remove_worktree(branch, force: options[:force], delete_branch: :merged) + end - if Git.merged?(branch) - Git.delete_branch(branch) - puts "Deleted merged branch: #{branch}" - end + desc "rmf BRANCH", "Force-remove a worktree (and merged branch)" + def rmf(branch) + remove_worktree(branch, force: true, delete_branch: :merged) + end - signal_cd(Git.main_worktree) + desc "rmrf BRANCH", "Force-remove a worktree and branch" + def rmrf(branch) + remove_worktree(branch, force: true, delete_branch: :always) end desc "prune", "Prune stale worktree admin files" @@ -215,6 +214,27 @@ def shellenv private + def remove_worktree(branch, force:, delete_branch:) + path = Git.worktree_path_for(branch) + abort "Error: No worktree found for branch: #{branch}" unless path + + Git.worktree_remove(path, force: force) + puts "Removed worktree: #{path}" + + case delete_branch + when :always + Git.delete_branch(branch, force: true) + puts "Deleted branch: #{branch}" + when :merged + if Git.merged?(branch) + Git.delete_branch(branch) + puts "Deleted merged branch: #{branch}" + end + end + + signal_cd(Git.main_worktree) + end + def signal_cd(path) cd_file = ENV["BONCHI_CD_FILE"] if cd_file diff --git a/lib/bonchi/git.rb b/lib/bonchi/git.rb index 79835d3..a96885d 100644 --- a/lib/bonchi/git.rb +++ b/lib/bonchi/git.rb @@ -69,8 +69,9 @@ def merged?(branch, into: default_base_branch) system("git", "merge-base", "--is-ancestor", branch, into) end - def delete_branch(branch) - system("git", "branch", "-d", branch) || abort("Failed to delete branch: #{branch}") + def delete_branch(branch, force: false) + flag = force ? "-D" : "-d" + system("git", "branch", flag, branch) || abort("Failed to delete branch: #{branch}") end def fetch_pr(pr_number) From 9820ef87b00b81599f3f1b8e472c458fddd76457 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 20:57:23 +0100 Subject: [PATCH 04/12] Run setup for pr-command just like create --- lib/bonchi/cli.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index 2d046ec..b2f9b7d 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -82,7 +82,12 @@ def switch(branch) The worktree branch will be named pr-. If the worktree already exists, switches to it instead. + + When a .worktree.yml exists in the main worktree, setup runs automatically. + Skip with --no-setup, or use --upto STEP to run only up to a specific step. DESC + option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree" + option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)" def pr(input) pr_number = extract_pr_number(input) branch = "pr-#{pr_number}" @@ -100,6 +105,11 @@ def pr(input) puts "PR ##{pr_number} checked out at: #{path}" signal_cd(path) + + if options[:setup] && Config.from_main_worktree + puts "" + Setup.new(worktree: path).run(upto: options[:upto]) + end end desc "init", "Generate a .worktree.yml in the current project" From c454ff355eeb3d0bd5c39b3a87d7375d57aff8b2 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 20:57:36 +0100 Subject: [PATCH 05/12] Update shell completions --- lib/bonchi/cli.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index b2f9b7d..11dc489 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -323,7 +323,7 @@ def extract_pr_number(input) COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - commands="create switch sw pr setup list ls remove rm prune shellenv help" + commands="create switch sw pr setup list ls remove rm rmf rmrf prune shellenv help" if [ $COMP_CWORD -eq 1 ]; then COMPREPLY=( $(compgen -W "$commands" -- "$cur") ) @@ -331,7 +331,7 @@ def extract_pr_number(input) fi case "$prev" in - switch|sw|remove|rm) + switch|sw|remove|rm|rmf|rmrf) local branches branches=$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +2) COMPREPLY=( $(compgen -W "$branches" -- "$cur") ) @@ -354,8 +354,10 @@ def extract_pr_number(input) 'setup:Run setup in current worktree' 'list:List all worktrees' 'ls:List all worktrees' - 'remove:Remove a worktree' - 'rm:Remove a worktree' + 'remove:Remove a worktree (and merged branch)' + 'rm:Remove a worktree (and merged branch)' + 'rmf:Force-remove a worktree (and merged branch)' + 'rmrf:Force-remove a worktree and branch' 'prune:Prune stale worktree admin files' 'shellenv:Output shell function for auto-cd' ) @@ -364,7 +366,7 @@ def extract_pr_number(input) _describe 'command' commands elif (( CURRENT == 3 )); then case "$words[2]" in - switch|sw|remove|rm) + switch|sw|remove|rm|rmf|rmrf) branches=(${(f)"$(git worktree list 2>/dev/null | sed -n 's/.*\[\([^]]*\)\].*/\1/p' | tail -n +1)"}) _describe 'branch' branches ;; From 3f9e00411a6da5b6080629b9ad680d812b7eedaa Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 21:11:20 +0100 Subject: [PATCH 06/12] create doesn't fail when branch exists --- lib/bonchi/cli.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index 11dc489..a436ef7 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -37,8 +37,13 @@ def create(branch, base = nil) return end - Git.worktree_add_new_branch(path, branch, base) - puts "Worktree created at: #{path}" + if Git.branch_exists?(branch) + Git.worktree_add(path, branch) + puts "Worktree created for existing branch at: #{path}" + else + Git.worktree_add_new_branch(path, branch, base) + puts "Worktree created at: #{path}" + end signal_cd(path) From 23a7137cc014f6c05b3d0dbf27a8e46effa89b5b Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 22:13:45 +0100 Subject: [PATCH 07/12] Revise README with updated bonchi command usage Updated usage instructions for bonchi commands with new options. --- README.md | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index e6ef299..cc97f0a 100644 --- a/README.md +++ b/README.md @@ -43,16 +43,19 @@ This gives you auto-cd (jumps into the worktree after create/switch/pr) and tab ## Usage ```sh -bonchi init # Generate a .worktree.yml in current project -bonchi create my-feature # New branch + worktree off default base -bonchi create my-feature develop # New branch off develop -bonchi switch existing-branch # Existing branch → new worktree -bonchi pr 123 # Checkout PR #123 -bonchi pr https://github.com/org/repo/pull/123 -bonchi list # List all worktrees -bonchi remove my-feature # Remove a worktree -bonchi prune # Clean up stale admin files -bonchi setup # Run setup in current worktree + bonchi init # Generate a .worktree.yml in the current project + bonchi create BRANCH [BASE] # Create new branch + worktree (alias for switch -c) + bonchi pr NUMBER_OR_URL # Checkout GitHub PR in worktree + bonchi switch BRANCH # Switch to branch in worktree + bonchi remove BRANCH # Remove a worktree (and merged branch) + bonchi rmf BRANCH # Force-remove a worktree (and merged branch) + bonchi rmrf BRANCH # Force-remove a worktree and branch + bonchi list # List all worktrees + bonchi setup [-- ARGS...] # Run setup in current worktree (ports, copy, pre_setup, setup cmd) + bonchi shellenv # Output shell function for auto-cd + completions + bonchi prune # Prune stale worktree admin files + bonchi version # Print version + bonchi help [COMMAND] # Describe available commands or one specific command ``` Run `bonchi help ` for detailed info on any command. From 232b779638c900b5a939b2cf568fbf0b2bc82342 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 21:27:04 +0100 Subject: [PATCH 08/12] Update descriptions --- lib/bonchi/cli.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index a436ef7..f0c623d 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -60,7 +60,12 @@ def create(branch, base = nil) The branch must already exist locally or on the remote. To create a new branch, use `bonchi create` instead. + + When a .worktree.yml exists in the main worktree, setup runs automatically. + Skip with --no-setup, or use --upto STEP to run only up to a specific step. DESC + option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree" + option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)" def switch(branch) existing = Git.worktree_path_for(branch) if existing @@ -78,12 +83,18 @@ def switch(branch) puts "Worktree created at: #{path}" signal_cd(path) + + if options[:setup] && Config.from_main_worktree + puts "" + Setup.new(worktree: path).run(upto: options[:upto]) + end end desc "pr NUMBER_OR_URL", "Checkout GitHub PR in worktree" long_desc <<~DESC - Fetch a GitHub pull request and check it out in a new worktree. + Fetch a GitHub pull request and switch to it in a new worktree. Accepts a PR number (e.g. 123) or a full GitHub PR URL. + Like `bonchi switch`, but fetches the PR first. The worktree branch will be named pr-. If the worktree already exists, switches to it instead. From a4711146f6182f4873084d9317b0dcc291ab1955 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 21:33:34 +0100 Subject: [PATCH 09/12] Make create alias for switch --- lib/bonchi/cli.rb | 75 +++++++++++++++++------------------------------ 1 file changed, 27 insertions(+), 48 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index f0c623d..91b34ff 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -15,20 +15,25 @@ def version map "--version" => :version map "-v" => :version - desc "create BRANCH [BASE]", "Create new branch + worktree" + desc "switch BRANCH", "Switch to branch in worktree" long_desc <<~DESC - Create a new branch and worktree. BASE defaults to the repository's default branch - (e.g. main). If a worktree for BRANCH already exists, switches to it instead. + Create a worktree for a branch and cd into it. + If a worktree for BRANCH already exists, switches to it instead. + + Use -c to create a new branch (like git switch -c). Use --base to specify + the base branch (defaults to the repository's default branch, e.g. main). - When a .worktree.yml exists in the main worktree, setup runs automatically - (copy files, allocate ports, run pre_setup and setup commands). + When a .worktree.yml exists in the main worktree, setup runs automatically. Skip with --no-setup, or use --upto STEP to run only up to a specific step. + + Aliases: sw, create (implies -c) DESC + option :c, type: :boolean, default: false, desc: "Create a new branch" + option :base, type: :string, desc: "Base branch for -c (default: repository default branch)" option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree" option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)" - def create(branch, base = nil) - base ||= Git.default_base_branch - path = Git.worktree_dir(branch) + def switch(branch) + abort "Error: --base requires -c flag" if options[:base] && !options[:c] existing = Git.worktree_path_for(branch) if existing @@ -37,14 +42,18 @@ def create(branch, base = nil) return end - if Git.branch_exists?(branch) + path = Git.worktree_dir(branch) + + if options[:c] + base = options[:base] || Git.default_base_branch + Git.worktree_add_new_branch(path, branch, base) + elsif Git.branch_exists?(branch) Git.worktree_add(path, branch) - puts "Worktree created for existing branch at: #{path}" else - Git.worktree_add_new_branch(path, branch, base) - puts "Worktree created at: #{path}" + abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi switch -c #{branch}' to create a new branch" end + puts "Worktree created at: #{path}" signal_cd(path) if options[:setup] && Config.from_main_worktree @@ -53,41 +62,11 @@ def create(branch, base = nil) end end - desc "switch BRANCH", "Switch to existing branch in worktree" - long_desc <<~DESC - Create a worktree for an existing branch and cd into it. - If a worktree for BRANCH already exists, switches to it instead. - - The branch must already exist locally or on the remote. - To create a new branch, use `bonchi create` instead. - - When a .worktree.yml exists in the main worktree, setup runs automatically. - Skip with --no-setup, or use --upto STEP to run only up to a specific step. - DESC + desc "create BRANCH [BASE]", "Create new branch + worktree (alias for switch -c)" option :setup, type: :boolean, default: true, desc: "Run setup after creating worktree" option :upto, type: :string, desc: "Run setup steps up to and including STEP (copy, link, ports, replace, pre_setup, setup)" - def switch(branch) - existing = Git.worktree_path_for(branch) - if existing - puts "Worktree already exists: #{existing}" - signal_cd(existing) - return - end - - unless Git.branch_exists?(branch) - abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi create #{branch}' to create a new branch" - end - - path = Git.worktree_dir(branch) - Git.worktree_add(path, branch) - puts "Worktree created at: #{path}" - - signal_cd(path) - - if options[:setup] && Config.from_main_worktree - puts "" - Setup.new(worktree: path).run(upto: options[:upto]) - end + def create(branch, base = nil) + invoke :switch, [branch], c: true, base: base, setup: options[:setup], upto: options[:upto] end desc "pr NUMBER_OR_URL", "Checkout GitHub PR in worktree" @@ -363,9 +342,9 @@ def extract_pr_number(input) _bonchi_complete_zsh() { local -a commands branches commands=( - 'create:Create new branch + worktree' - 'switch:Switch to existing branch in worktree' - 'sw:Switch to existing branch in worktree' + 'create:Create new branch + worktree (alias for switch -c)' + 'switch:Switch to branch in worktree (-c to create)' + 'sw:Switch to branch in worktree (-c to create)' 'pr:Checkout GitHub PR in worktree' 'setup:Run setup in current worktree' 'list:List all worktrees' From e5569edea58e9265bfb22c409ddf5dfa6f20a7cd Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 21:59:37 +0100 Subject: [PATCH 10/12] create is like switch but creates what's missing --- lib/bonchi/cli.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index 91b34ff..c274ef6 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -44,10 +44,10 @@ def switch(branch) path = Git.worktree_dir(branch) - if options[:c] + if options[:c] && !Git.branch_exists?(branch) base = options[:base] || Git.default_base_branch Git.worktree_add_new_branch(path, branch, base) - elsif Git.branch_exists?(branch) + elsif options[:c] || Git.branch_exists?(branch) Git.worktree_add(path, branch) else abort "Error: Branch '#{branch}' does not exist\nUse 'bonchi switch -c #{branch}' to create a new branch" From 6fde09f0ee77d0a2f547512ee11bda5ecb9b6636 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 22:41:24 +0100 Subject: [PATCH 11/12] Don't show built-in command --- lib/bonchi/cli.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bonchi/cli.rb b/lib/bonchi/cli.rb index c274ef6..6dc1afe 100644 --- a/lib/bonchi/cli.rb +++ b/lib/bonchi/cli.rb @@ -217,6 +217,8 @@ def shellenv map "ls" => :list map "rm" => :remove + remove_command :tree + private def remove_worktree(branch, force:, delete_branch:) From a0fba7c69ba2b72534e23d4950ca3f945bc5a383 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Wed, 18 Mar 2026 22:45:13 +0100 Subject: [PATCH 12/12] Fix formatting and clarify release workflow steps Correct formatting in README and clarify steps for release workflow. --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc97f0a..5cf4127 100644 --- a/README.md +++ b/README.md @@ -169,15 +169,16 @@ Using [mise](https://mise.jdx.dev/) for env-vars is recommended. 1. Update `lib/bonchi/version.rb` ``` bin/rake 'gem:write_version[0.5.0]' - # commit & push + # commit&push + # check CI ``` -1. Release workflow from GitHub Actions... - - ...publishes to RubyGems (with Sigstore attestation) - - ...creates git GitHub release after successful publish 1. Tag ``` gem_push=no bin/rake release ``` +1. Release workflow from GitHub Actions... + - ...publishes to RubyGems (with Sigstore attestation) + - ...creates git GitHub release after successful publish 1. Update `version.rb` for next dev-cycle ``` bin/rake 'gem:write_version[0.6.0.dev]'