gempilot 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/skills/using-command-kit/SKILL.md +119 -0
  3. data/.claude/skills/using-command-kit/cli-example.rb +84 -0
  4. data/.claude/skills/using-command-kit/generator-pattern.rb +62 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +281 -0
  7. data/.ruby-version +1 -0
  8. data/CLAUDE.md +45 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +140 -0
  11. data/Rakefile +44 -0
  12. data/data/templates/gem/Gemfile.erb +23 -0
  13. data/data/templates/gem/LICENSE.txt.erb +21 -0
  14. data/data/templates/gem/README.md.erb +25 -0
  15. data/data/templates/gem/Rakefile.erb +36 -0
  16. data/data/templates/gem/bin/console.erb +7 -0
  17. data/data/templates/gem/bin/setup.erb +5 -0
  18. data/data/templates/gem/dotfiles/github/workflows/ci.yml.erb +33 -0
  19. data/data/templates/gem/dotfiles/gitignore +11 -0
  20. data/data/templates/gem/dotfiles/rubocop.yml.erb +209 -0
  21. data/data/templates/gem/dotfiles/ruby-version.erb +1 -0
  22. data/data/templates/gem/exe/gem_name.erb +3 -0
  23. data/data/templates/gem/gemspec.erb +27 -0
  24. data/data/templates/gem/lib/gem_name/version.rb.erb +7 -0
  25. data/data/templates/gem/lib/gem_name.rb.erb +16 -0
  26. data/data/templates/gem/lib/gem_name_extension.rb.erb +20 -0
  27. data/data/templates/gem/rspec.erb +3 -0
  28. data/data/templates/gem/spec/gem_name_spec.rb.erb +5 -0
  29. data/data/templates/gem/spec/spec_helper.rb.erb +10 -0
  30. data/data/templates/gem/spec/zeitwerk_spec.rb.erb +5 -0
  31. data/data/templates/gem/test/gem_name_test.rb.erb +7 -0
  32. data/data/templates/gem/test/test_helper.rb.erb +7 -0
  33. data/data/templates/gem/test/zeitwerk_test.rb.erb +9 -0
  34. data/data/templates/new/.keep +0 -0
  35. data/data/templates/new/command.rb.erb +15 -0
  36. data/docs/command_kit_comparison.md +249 -0
  37. data/docs/command_kit_reference.md +517 -0
  38. data/docs/plans/2026-02-18-gempilot-add-command.md +718 -0
  39. data/docs/superpowers/plans/2026-04-01-rubocop-new-config.md +838 -0
  40. data/docs/superpowers/plans/2026-04-06-dogfood-inflectable.md +659 -0
  41. data/docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md +166 -0
  42. data/docs/superpowers/plans/2026-04-06-integrate-version-tools.md +162 -0
  43. data/docs/superpowers/plans/2026-04-06-new-readme.md +185 -0
  44. data/docs/version-management-redesign.md +44 -0
  45. data/exe/gempilot +12 -0
  46. data/issues.rec +77 -0
  47. data/lib/core_ext/string/inflection_methods.rb +68 -0
  48. data/lib/core_ext/string/refinements/inflectable.rb +15 -0
  49. data/lib/gempilot/cli/command.rb +17 -0
  50. data/lib/gempilot/cli/commands/bump.rb +49 -0
  51. data/lib/gempilot/cli/commands/console.rb +38 -0
  52. data/lib/gempilot/cli/commands/create.rb +183 -0
  53. data/lib/gempilot/cli/commands/destroy.rb +136 -0
  54. data/lib/gempilot/cli/commands/new.rb +226 -0
  55. data/lib/gempilot/cli/commands/release.rb +40 -0
  56. data/lib/gempilot/cli/gem_builder.rb +105 -0
  57. data/lib/gempilot/cli/gem_context.rb +40 -0
  58. data/lib/gempilot/cli/generator.rb +90 -0
  59. data/lib/gempilot/cli.rb +19 -0
  60. data/lib/gempilot/github_release.rb +30 -0
  61. data/lib/gempilot/project/version.rb +39 -0
  62. data/lib/gempilot/project.rb +111 -0
  63. data/lib/gempilot/strict_shell.rb +18 -0
  64. data/lib/gempilot/version.rb +3 -0
  65. data/lib/gempilot/version_tag.rb +53 -0
  66. data/lib/gempilot/version_task.rb +108 -0
  67. data/lib/gempilot.rb +17 -0
  68. data/notes.md +31 -0
  69. metadata +165 -0
@@ -0,0 +1,226 @@
1
+ module Gempilot
2
+ class CLI
3
+ module Commands
4
+ ## Generates a new class, module, or command inside an existing gem.
5
+ class New < Command
6
+ using String::Inflectable
7
+
8
+ include Generator
9
+ include GemContext
10
+
11
+ template_dir File.join(Gempilot::ROOT, "data", "templates", "new")
12
+
13
+ usage "[options] TYPE CONSTANT"
14
+ description "Generate a class, module, or command in an existing gem"
15
+
16
+ examples [
17
+ "class MyGem::Authentication",
18
+ "class MyGem::Services::Authentication",
19
+ "module MyGem::Middleware",
20
+ "command deploy",
21
+ ]
22
+
23
+ argument :type, required: false,
24
+ desc: "Type to generate (class, module, command)"
25
+
26
+ argument :path, required: false,
27
+ desc: "Fully-qualified constant (e.g., MyGem::Services::Auth) or command name"
28
+
29
+ def run(type = nil, path = nil)
30
+ type ||= prompt_for_type
31
+ detect_gem_context
32
+ path ||= prompt_for_path
33
+ dispatch_add(type, path)
34
+ end
35
+
36
+ private
37
+
38
+ def prompt_for_type
39
+ puts colors.bright_black("What kind of component do you want to add?")
40
+ ask_multiple_choice(colors.green("Type"), %w[class module command])
41
+ end
42
+
43
+ def prompt_for_path
44
+ puts
45
+ puts colors.bright_black("Fully-qualified constant name (e.g., #{@gem_module}::Services::Authentication).")
46
+ ask(colors.green("Constant"), required: true)
47
+ end
48
+
49
+ def dispatch_add(type, path)
50
+ case type
51
+ when "class" then add_class(path)
52
+ when "module" then add_module(path)
53
+ when "command" then add_command(path)
54
+ else
55
+ puts colors.red("Unknown type '#{type}'. Use class, module, or command.")
56
+ exit 1
57
+ end
58
+ end
59
+
60
+ def build_namespace_lines(namespaces)
61
+ namespaces.each_with_index.map do |ns, i|
62
+ "#{" " * i}module #{ns}"
63
+ end
64
+ end
65
+
66
+ def build_closing_lines(namespaces)
67
+ Array.new(namespaces.length) do |i|
68
+ "#{" " * (namespaces.length - 1 - i)}end"
69
+ end
70
+ end
71
+
72
+ def build_nested_source(namespaces, type_keyword, name)
73
+ depth = namespaces.length
74
+ lines = build_namespace_lines(namespaces)
75
+ lines << "#{" " * depth}#{type_keyword} #{name}"
76
+ lines << "#{" " * depth}end"
77
+ lines.concat(build_closing_lines(namespaces))
78
+ "#{lines.join("\n")}\n"
79
+ end
80
+
81
+ def print_adding_banner(kind, label)
82
+ puts
83
+ puts colors.bright_white("Adding #{kind} ") +
84
+ colors.bold(colors.cyan(label)) +
85
+ colors.bright_white("...")
86
+ puts
87
+ end
88
+
89
+ def prepare_constant(constant)
90
+ namespaces, name, segments = parse_constant(constant)
91
+ validate_gem_root!(namespaces.first)
92
+ file_path = "#{File.join("lib", *segments)}.rb"
93
+ ensure_directory(File.dirname(file_path))
94
+ [namespaces, name, segments, file_path]
95
+ end
96
+
97
+ def ensure_directory(dir)
98
+ mkdir(dir) unless File.directory?(dir)
99
+ end
100
+
101
+ def add_class(constant)
102
+ namespaces, class_name, segments, file_path = prepare_constant(constant)
103
+ print_adding_banner("class", "#{namespaces.join("::")}::#{class_name}")
104
+ source = build_nested_source(namespaces, "class", class_name)
105
+ create_file(file_path, source)
106
+ add_test_file(namespaces, class_name, segments)
107
+ end
108
+
109
+ def add_module(constant)
110
+ namespaces, mod_name, _segments, file_path = prepare_constant(constant)
111
+ print_adding_banner("module", "#{namespaces.join("::")}::#{mod_name}")
112
+ source = build_nested_source(namespaces, "module", mod_name)
113
+ create_file(file_path, source)
114
+ end
115
+
116
+ def add_command(name)
117
+ file_name = name.underscore
118
+ command_name = name.camelize
119
+ file_path = File.join("lib", @require_path, "cli", "commands", "#{file_name}.rb")
120
+
121
+ print_adding_banner("command", command_name)
122
+ ensure_directory(File.dirname(file_path))
123
+
124
+ @command_name = command_name
125
+ @command_file_name = file_name
126
+ erb "command.rb.erb", file_path
127
+ add_command_test_file(command_name, file_name)
128
+ end
129
+
130
+ def command_test_path(file_name)
131
+ if @test_framework == :rspec
132
+ File.join("spec", @require_path, "cli", "commands", "#{file_name}_spec.rb")
133
+ else
134
+ File.join("test", @require_path, "cli", "commands", "#{file_name}_test.rb")
135
+ end
136
+ end
137
+
138
+ def rspec_command_content(command_name)
139
+ <<~RUBY
140
+ require "spec_helper"
141
+
142
+ RSpec.describe #{@gem_module}::CLI::Commands::#{command_name} do
143
+ it "is defined" do
144
+ expect(described_class).not_to be_nil
145
+ end
146
+ end
147
+ RUBY
148
+ end
149
+
150
+ def minitest_command_content(command_name)
151
+ <<~RUBY
152
+ require "test_helper"
153
+ require "#{@require_path}/cli"
154
+ require "stringio"
155
+
156
+ module #{@gem_module}
157
+ class CLI
158
+ class #{command_name}Test < Minitest::Test
159
+ def test_placeholder
160
+ stdout = StringIO.new
161
+ command = Commands::#{command_name}.new(stdout: stdout)
162
+ assert command
163
+ end
164
+ end
165
+ end
166
+ end
167
+ RUBY
168
+ end
169
+
170
+ def add_command_test_file(command_name, file_name)
171
+ test_path = command_test_path(file_name)
172
+ ensure_directory(File.dirname(test_path))
173
+ content = if @test_framework == :rspec
174
+ rspec_command_content(command_name)
175
+ else
176
+ minitest_command_content(command_name)
177
+ end
178
+ create_file(test_path, content)
179
+ end
180
+
181
+ def class_test_path(segments)
182
+ if @test_framework == :rspec
183
+ "#{File.join("spec", @require_path, *segments[1..])}_spec.rb"
184
+ else
185
+ "#{File.join("test", @require_path, *segments[1..])}_test.rb"
186
+ end
187
+ end
188
+
189
+ def rspec_class_content(namespaces, class_name)
190
+ <<~RUBY
191
+ require "spec_helper"
192
+
193
+ RSpec.describe #{namespaces.join("::")}::#{class_name} do
194
+ pending "add some examples"
195
+ end
196
+ RUBY
197
+ end
198
+
199
+ def minitest_class_content(namespaces, class_name)
200
+ <<~RUBY
201
+ require "test_helper"
202
+
203
+ module #{namespaces.first}
204
+ class #{(namespaces[1..] + [class_name]).join("::")}Test < Minitest::Test
205
+ def test_placeholder
206
+ assert true
207
+ end
208
+ end
209
+ end
210
+ RUBY
211
+ end
212
+
213
+ def add_test_file(namespaces, class_name, segments)
214
+ test_path = class_test_path(segments)
215
+ ensure_directory(File.dirname(test_path))
216
+ content = if @test_framework == :rspec
217
+ rspec_class_content(namespaces, class_name)
218
+ else
219
+ minitest_class_content(namespaces, class_name)
220
+ end
221
+ create_file(test_path, content)
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,40 @@
1
+ require "bundler"
2
+
3
+ module Gempilot
4
+ class CLI
5
+ module Commands
6
+ ## Delegates to +rake release+ to build and push the gem.
7
+ class Release < Command
8
+ include GemContext
9
+
10
+ description "Release the gem (delegates to rake release)"
11
+
12
+ def run
13
+ detect_gem_context
14
+ print_release_banner
15
+ run_rake_release
16
+ end
17
+
18
+ private
19
+
20
+ def print_release_banner
21
+ puts colors.bright_white("Releasing ") +
22
+ colors.bold(colors.cyan(@gem_name)) +
23
+ colors.bright_white("...")
24
+ puts
25
+ end
26
+
27
+ def run_rake_release
28
+ success = Bundler.with_unbundled_env do
29
+ system("bundle", "exec", "rake", "release")
30
+ end
31
+
32
+ return if success
33
+
34
+ puts colors.red("Release failed. Check the output above for errors.")
35
+ exit 1
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,105 @@
1
+ module Gempilot
2
+ class CLI
3
+ ## File rendering and directory creation logic for scaffolding a new gem.
4
+ ##
5
+ ## Expects the including class to provide Generator methods (+mkdir+, +erb+,
6
+ ## +chmod+, +cp+, +cd+, +sh+) and the following instance variables:
7
+ ## +@gem_name+, +@require_path+, +@module_name+, +@hyphenated+,
8
+ ## +@test_framework+, +@branch+.
9
+ module GemBuilder
10
+ private
11
+
12
+ def create_directories
13
+ mkdir @gem_name
14
+ mkdir "#{@gem_name}/lib"
15
+ create_lib_subdirectories
16
+ mkdir "#{@gem_name}/bin"
17
+ mkdir "#{@gem_name}/exe" if options[:exe]
18
+ create_test_directory
19
+ end
20
+
21
+ def create_lib_subdirectories
22
+ @require_path.split("/").reduce("#{@gem_name}/lib") do |dir, part|
23
+ path = "#{dir}/#{part}"
24
+ mkdir path
25
+ path
26
+ end
27
+ end
28
+
29
+ def create_test_directory
30
+ mkdir @test_framework == :rspec ? "#{@gem_name}/spec" : "#{@gem_name}/test"
31
+ end
32
+
33
+ def render_core_templates
34
+ erb "gemspec.erb", "#{@gem_name}/#{@gem_name}.gemspec"
35
+ erb "Gemfile.erb", "#{@gem_name}/Gemfile"
36
+ erb "Rakefile.erb", "#{@gem_name}/Rakefile"
37
+ erb "README.md.erb", "#{@gem_name}/README.md"
38
+ erb "LICENSE.txt.erb", "#{@gem_name}/LICENSE.txt"
39
+ erb "lib/gem_name.rb.erb", "#{@gem_name}/lib/#{@gem_name}.rb"
40
+ erb "lib/gem_name/version.rb.erb", "#{@gem_name}/lib/#{@require_path}/version.rb"
41
+ erb "lib/gem_name_extension.rb.erb", "#{@gem_name}/lib/#{@require_path}.rb" if @hyphenated
42
+ end
43
+
44
+ def render_test_templates
45
+ @test_framework == :rspec ? render_rspec_templates : render_minitest_templates
46
+ end
47
+
48
+ def render_rspec_templates
49
+ erb "spec/spec_helper.rb.erb", "#{@gem_name}/spec/spec_helper.rb"
50
+ erb "spec/gem_name_spec.rb.erb", "#{@gem_name}/spec/#{@gem_name.tr("-", "_")}_spec.rb"
51
+ erb "spec/zeitwerk_spec.rb.erb", "#{@gem_name}/spec/zeitwerk_spec.rb"
52
+ erb "rspec.erb", "#{@gem_name}/.rspec"
53
+ end
54
+
55
+ def render_minitest_templates
56
+ erb "test/test_helper.rb.erb", "#{@gem_name}/test/test_helper.rb"
57
+ erb "test/gem_name_test.rb.erb", "#{@gem_name}/test/#{@gem_name.tr("-", "_")}_test.rb"
58
+ erb "test/zeitwerk_test.rb.erb", "#{@gem_name}/test/zeitwerk_test.rb"
59
+ end
60
+
61
+ def render_dev_files
62
+ erb "bin/console.erb", "#{@gem_name}/bin/console"
63
+ erb "bin/setup.erb", "#{@gem_name}/bin/setup"
64
+ chmod "+x", "#{@gem_name}/bin/console"
65
+ chmod "+x", "#{@gem_name}/bin/setup"
66
+ end
67
+
68
+ def render_config_files
69
+ erb "dotfiles/rubocop.yml.erb", "#{@gem_name}/.rubocop.yml"
70
+ cp "dotfiles/gitignore", "#{@gem_name}/.gitignore"
71
+ erb "dotfiles/ruby-version.erb", "#{@gem_name}/.ruby-version"
72
+ render_ci_workflow
73
+ end
74
+
75
+ def render_ci_workflow
76
+ mkdir "#{@gem_name}/.github"
77
+ mkdir "#{@gem_name}/.github/workflows"
78
+ erb "dotfiles/github/workflows/ci.yml.erb", "#{@gem_name}/.github/workflows/ci.yml"
79
+ end
80
+
81
+ def render_executable
82
+ return unless options[:exe]
83
+
84
+ erb "exe/gem_name.erb", "#{@gem_name}/exe/#{@gem_name}"
85
+ chmod "+x", "#{@gem_name}/exe/#{@gem_name}"
86
+ end
87
+
88
+ def run_bundle_install
89
+ cd @gem_name do
90
+ sh "bundle", "install"
91
+ end
92
+ end
93
+
94
+ def initialize_git_repo
95
+ return unless options[:git]
96
+
97
+ cd @gem_name do
98
+ sh "git", "init", "-q", "-b", @branch
99
+ sh "git", "add", "."
100
+ sh "git", "commit", "-q", "-m", "Initial commit."
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,40 @@
1
+ module Gempilot
2
+ class CLI
3
+ ## Shared context for commands that operate inside an existing gem.
4
+ module GemContext
5
+ using String::Inflectable
6
+
7
+ private
8
+
9
+ def detect_gem_context
10
+ gemspec = Dir.glob("*.gemspec").first
11
+
12
+ unless gemspec
13
+ puts colors.red("No gemspec found in current directory. Run this from your gem's root.")
14
+ exit 1
15
+ end
16
+
17
+ @gem_name = File.basename(gemspec, ".gemspec")
18
+ @require_path = @gem_name.tr("-", "/")
19
+ @gem_module = @require_path.camelize
20
+ @test_framework = File.directory?("spec") ? :rspec : :minitest
21
+ end
22
+
23
+ def parse_constant(constant)
24
+ parts = constant.split("::")
25
+ namespaces = parts[0...-1]
26
+ name = parts.last
27
+ segments = parts.map(&:underscore)
28
+ [namespaces, name, segments]
29
+ end
30
+
31
+ def validate_gem_root!(root)
32
+ expected = @gem_module.split("::").first
33
+ return if root == expected
34
+
35
+ puts colors.red("Expected constant to start with #{expected}, got #{root}")
36
+ exit 1
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,90 @@
1
+ require "bundler"
2
+ require "command_kit/colors"
3
+ require "command_kit/file_utils"
4
+ require "fileutils"
5
+ require "erb"
6
+
7
+ module Gempilot
8
+ class CLI
9
+ ## File generation utilities for scaffolding gems and components.
10
+ module Generator
11
+ include CommandKit::Colors
12
+ include CommandKit::FileUtils
13
+
14
+ def self.included(command)
15
+ command.extend ClassMethods
16
+ end
17
+
18
+ ## Class-level helpers for Generator, including +template_dir+ resolution.
19
+ module ClassMethods
20
+ def template_dir(path = nil)
21
+ if path
22
+ @template_dir = File.expand_path(path)
23
+ else
24
+ @template_dir || (superclass.template_dir if superclass.respond_to?(:template_dir))
25
+ end
26
+ end
27
+ end
28
+
29
+ attr_reader :template_dir
30
+
31
+ def initialize(**kwargs)
32
+ super
33
+
34
+ return if (@template_dir = self.class.template_dir)
35
+
36
+ raise NotImplementedError, "#{self.class} did not define template_dir"
37
+ end
38
+
39
+ def print_action(command, dest, source: nil)
40
+ line = +""
41
+ line << "\t" << colors.bold(colors.green(command))
42
+ line << "\t" << colors.green(source) if source
43
+ line << "\t" << colors.green(dest) if dest
44
+
45
+ puts(line)
46
+ end
47
+
48
+ def mkdir(path)
49
+ print_action "mkdir", path
50
+ ::FileUtils.mkdir_p(path)
51
+ end
52
+
53
+ def touch(path)
54
+ print_action "touch", path
55
+ ::FileUtils.touch(path)
56
+ end
57
+
58
+ def create_file(path, content)
59
+ print_action "create", path
60
+ File.write(path, content)
61
+ end
62
+
63
+ def chmod(mode, path)
64
+ print_action "chmod", path
65
+ ::FileUtils.chmod(mode, path)
66
+ end
67
+
68
+ def cp(source, dest)
69
+ print_action "cp", dest, source: source
70
+ ::FileUtils.cp(File.join(@template_dir, source), dest)
71
+ end
72
+
73
+ def erb(source, dest = nil)
74
+ print_action("erb", dest, source: source) if dest
75
+
76
+ source_path = File.join(@template_dir, source)
77
+ super(source_path, dest)
78
+ end
79
+
80
+ def sh(command, *arguments)
81
+ print_action "run", [command, *arguments].join(" ")
82
+ success = Bundler.with_unbundled_env do
83
+ system(command, *arguments)
84
+ end
85
+ puts colors.yellow("Warning: '#{[command, *arguments].join(" ")}' exited with non-zero status") unless success
86
+ success
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ require "command_kit/commands"
2
+ require "command_kit/commands/auto_load"
3
+ require "command_kit/options/version"
4
+ require_relative "../gempilot"
5
+
6
+ module Gempilot
7
+ ## Top-level command router for the gempilot CLI.
8
+ class CLI
9
+ include CommandKit::Commands
10
+ include CommandKit::Commands::AutoLoad.new(
11
+ dir: "#{__dir__}/cli/commands",
12
+ namespace: "#{self}::Commands",
13
+ )
14
+ include CommandKit::Options::Version
15
+
16
+ command_name "gempilot"
17
+ version Gempilot::VERSION
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Gempilot
2
+ ## Manages GitHub releases for a version tag.
3
+ class GithubRelease
4
+ include StrictShell
5
+
6
+ attr_reader :tag
7
+
8
+ def initialize(tag)
9
+ @tag = tag
10
+ end
11
+
12
+ def create
13
+ sh "git", "push"
14
+ sh "git", "push", "--tags"
15
+ sh "gh", "release", "create",
16
+ "--generate-notes", "--fail-on-no-commits",
17
+ tag
18
+ end
19
+
20
+ def destroy
21
+ sh "gh", "release", "delete",
22
+ "--yes", "--cleanup-tag",
23
+ tag
24
+ end
25
+
26
+ def list
27
+ sh "gh", "release", "list"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,39 @@
1
+ module Gempilot
2
+ class Project
3
+ Version = Data.define(:path, :value) do
4
+ def tag
5
+ "v#{value}"
6
+ end
7
+
8
+ def bump(segment = :patch)
9
+ case segment.to_sym
10
+ when :major, :minor, :patch then bump_semver(segment)
11
+ when :dev then bump_dev
12
+ else raise ArgumentError, "Unknown segment #{segment.inspect}. Use :major, :minor, :patch, or :dev"
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def bump_semver(segment)
19
+ major, minor, patch = value.split(".").first(3).map(&:to_i)
20
+ new_value = case segment.to_sym
21
+ when :major then "#{major + 1}.0.0"
22
+ when :minor then "#{major}.#{minor + 1}.0"
23
+ when :patch then "#{major}.#{minor}.#{patch + 1}"
24
+ end
25
+ with(value: new_value)
26
+ end
27
+
28
+ def bump_dev
29
+ if value.match?(/\.dev\d+\z/)
30
+ with(value: value.sub(/\d+\z/) { it.to_i + 1 })
31
+ else
32
+ with(value: "#{value}.dev1")
33
+ end
34
+ end
35
+
36
+ alias_method :next_version, :bump
37
+ end.freeze
38
+ end
39
+ end