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,111 @@
1
+ require "pathname"
2
+ require "warning"
3
+
4
+ module Gempilot
5
+ ## Introspects a gem project to discover its name, module, and version.
6
+ class Project
7
+ class ProjectIntrospectionError < StandardError; end
8
+
9
+ REDEFINITION_WARNING = /previous definition of VERSION was here/
10
+ REINITIALIZATION_WARNING = /already initialized constant [^\s]+::VERSION/
11
+ private_constant :REDEFINITION_WARNING, :REINITIALIZATION_WARNING
12
+
13
+ using String::Inflectable
14
+
15
+ attr_reader :root
16
+
17
+ def initialize(root = Dir.pwd)
18
+ @root = Pathname(root)
19
+ @verifications = Set.new
20
+ end
21
+
22
+ def lib
23
+ root.join("lib")
24
+ .tap { verify_existence! it }
25
+ end
26
+
27
+ def lib_project
28
+ @lib_project ||= fetch_lib_project
29
+ end
30
+
31
+ def name
32
+ lib_project.basename.to_s
33
+ end
34
+
35
+ def klass
36
+ Object.const_get(name.camelize)
37
+ end
38
+
39
+ def version
40
+ @version ||= fetch_version
41
+ end
42
+
43
+ def refresh_version!
44
+ @version = fetch_version
45
+ end
46
+
47
+ def increment_version
48
+ version.next_version
49
+ end
50
+
51
+ def version_tag = version.tag
52
+
53
+ def version_value = version.value
54
+
55
+ def write_version!(old_version, new_version)
56
+ with_version_file do |f|
57
+ source = f.read
58
+
59
+ unless source.match?(Regexp.escape(old_version.value))
60
+ abort "Expected to find #{old_version.value} in #{f.path} but did not"
61
+ end
62
+
63
+ f.rewind
64
+ f.write source.gsub(old_version.value, new_version.value)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def with_version_file
71
+ version.path.open(File::RDWR, 0o644) do |f|
72
+ f.flock File::LOCK_EX
73
+ yield f
74
+ f.truncate(f.pos)
75
+ end
76
+ end
77
+
78
+ def fetch_lib_project
79
+ files = lib.glob("*.rb")
80
+ dirs = files.map { it.sub_ext("") }.select(&:directory?)
81
+ case dirs.count
82
+ in 0 then raise ProjectIntrospectionError, "Could not identify project dir"
83
+ in (2..)
84
+ msg = "Found more than one possible project name:\n - #{dirs.join("\n - ")}"
85
+ raise ProjectIntrospectionError, msg
86
+ in 1 then dirs.first
87
+ end
88
+ end
89
+
90
+ def fetch_version
91
+ Warning.ignore(REDEFINITION_WARNING)
92
+ Warning.ignore(REINITIALIZATION_WARNING)
93
+ path = lib_project
94
+ .join("version.rb")
95
+ .tap { verify_existence! it }
96
+ .tap { load it }
97
+
98
+ value = klass.const_get(:VERSION)
99
+
100
+ Version.new(path:, value:)
101
+ end
102
+
103
+ def verify_existence!(path)
104
+ return true if @verifications.member? path
105
+
106
+ raise ProjectIntrospectionError, "Expected #{path} to exist but does not" unless path.exist?
107
+
108
+ @verifications.add path
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,18 @@
1
+ require "rake"
2
+
3
+ module Gempilot
4
+ ## Wraps Rake's sh to raise on non-zero exit status.
5
+ module StrictShell
6
+ include Rake::FileUtilsExt
7
+
8
+ private
9
+
10
+ def sh(*args, **kwargs)
11
+ super do |ok, res|
12
+ raise "Command #{args.join(" ").inspect} failed (exit #{res.exitstatus})" unless ok
13
+
14
+ [ok, res]
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Gempilot
2
+ VERSION = "0.2.0".freeze
3
+ end
@@ -0,0 +1,53 @@
1
+ require "open3"
2
+
3
+ module Gempilot
4
+ ## Manages git operations for version releases.
5
+ class VersionTag
6
+ include StrictShell
7
+
8
+ attr_reader :version
9
+
10
+ def initialize(version)
11
+ @version = version
12
+ end
13
+
14
+ def create
15
+ _, _, status = Open3.capture3("git", "diff", "--staged", "--quiet")
16
+ raise "Cannot proceed, staging area must be clean" unless status.success?
17
+
18
+ sh "git", "add", version.path.to_s
19
+ sh "git", "commit", "-m", "Bump version to #{version.value}"
20
+ end
21
+
22
+ def tag
23
+ assert_last_commit_is_bump!
24
+ sh "git", "tag", version.tag
25
+ end
26
+
27
+ def untag
28
+ assert_last_commit_is_bump!
29
+ sh "git", "tag", "--delete", version.tag
30
+ end
31
+
32
+ def reset
33
+ assert_last_commit_is_bump!
34
+ sh "git", "reset", "--quiet", "--mixed", "HEAD~1"
35
+ sh "git", "restore", version.path.to_s
36
+ end
37
+
38
+ def revert
39
+ assert_last_commit_is_bump!
40
+ sh "git", "revert", "HEAD", "--no-edit"
41
+ end
42
+
43
+ private
44
+
45
+ def assert_last_commit_is_bump!
46
+ message, status = Open3.capture2("git", "log", "-1", "--pretty=%B")
47
+ raise "Failed to read last commit message" unless status.success?
48
+
49
+ message.strip!
50
+ abort "Last commit does not appear to be a version bump." unless message.start_with?("Bump version to ")
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,108 @@
1
+ require "rake/tasklib"
2
+ require_relative "../gempilot"
3
+
4
+ module Gempilot
5
+ ## Rake tasks for version lifecycle management.
6
+ class VersionTask < Rake::TaskLib
7
+ attr_reader :project
8
+
9
+ def initialize(root: Dir.pwd)
10
+ super()
11
+ @project = Project.new(root)
12
+ define_tasks
13
+ end
14
+
15
+ private
16
+
17
+ def define_tasks
18
+ define_version_tasks
19
+ define_version_composite_tasks
20
+ define_github_tasks
21
+ end
22
+
23
+ def define_version_tasks
24
+ project = @project
25
+
26
+ namespace :version do
27
+ define_current_task(project)
28
+ define_bump_task(project)
29
+ define_commit_task(project)
30
+ define_tag_task(project)
31
+ define_untag_task(project)
32
+ define_reset_task(project)
33
+ define_revert_task(project)
34
+ end
35
+ end
36
+
37
+ def define_version_composite_tasks
38
+ desc "Bump version, commit, and tag (rake version:release[minor])"
39
+ task "version:release", [:segment] => ["version:bump", "version:commit", "version:tag"]
40
+
41
+ desc "Untag and reset version"
42
+ task "version:unrelease" => ["version:untag", "version:reset"]
43
+ end
44
+
45
+ def define_current_task(project)
46
+ desc "Display the current version"
47
+ task(:current) { puts "Current version: #{project.version_value}" }
48
+ end
49
+
50
+ def define_bump_task(project)
51
+ desc "Bump version (patch default, or rake version:bump[minor])"
52
+ task :bump, [:segment] do |_t, args|
53
+ segment = (args[:segment] || :patch).to_sym
54
+ old_version = project.version
55
+ new_version = old_version.bump(segment)
56
+ project.write_version!(old_version, new_version)
57
+ project.refresh_version!
58
+ puts "Version bumped from #{old_version.value} to #{project.version_value}"
59
+ end
60
+ end
61
+
62
+ def define_commit_task(project)
63
+ desc "Commit the version change"
64
+ task(:commit) { VersionTag.new(project.version).create }
65
+ end
66
+
67
+ def define_tag_task(project)
68
+ desc "Tag the current version"
69
+ task(:tag) { VersionTag.new(project.version).tag }
70
+ end
71
+
72
+ def define_untag_task(project)
73
+ desc "Untag the current version"
74
+ task(:untag) { VersionTag.new(project.version).untag }
75
+ end
76
+
77
+ def define_reset_task(project)
78
+ desc "Reset the last version bump commit"
79
+ task :reset do
80
+ VersionTag.new(project.version).reset
81
+ project.refresh_version!
82
+ end
83
+ end
84
+
85
+ def define_revert_task(project)
86
+ desc "Revert the last version bump commit"
87
+ task :revert do
88
+ VersionTag.new(project.version).revert
89
+ project.refresh_version!
90
+ end
91
+ end
92
+
93
+ def define_github_tasks
94
+ project = @project
95
+
96
+ namespace "version:github" do
97
+ desc "Create a GitHub release for the current version"
98
+ task(:release) { GithubRelease.new(project.version_tag).create }
99
+
100
+ desc "Delete the GitHub release for the current version"
101
+ task(:unrelease) { GithubRelease.new(project.version_tag).destroy }
102
+
103
+ desc "List GitHub releases"
104
+ task(:list) { GithubRelease.new(project.version_tag).list }
105
+ end
106
+ end
107
+ end
108
+ end
data/lib/gempilot.rb ADDED
@@ -0,0 +1,17 @@
1
+ require "zeitwerk"
2
+
3
+ autoload :Open3, "open3"
4
+
5
+ # CLI toolkit for creating and managing Ruby gems.
6
+ module Gempilot
7
+ ROOT = Pathname(__dir__).parent.expand_path.freeze
8
+ LOADER = Zeitwerk::Loader.for_gem.tap do |l|
9
+ l.inflector.inflect("cli" => "CLI")
10
+ l.push_dir ROOT.join("lib/core_ext")
11
+ l.collapse ROOT.join("lib/core_ext/*/refinements")
12
+ l.setup
13
+ end
14
+
15
+ class Error < StandardError; end
16
+ class CommandError < Error; end
17
+ end
data/notes.md ADDED
@@ -0,0 +1,31 @@
1
+ # Setup todos
2
+ * Remove .bak.spec file
3
+ * Add files to gitignore
4
+ * /.idea
5
+ * Gemfile.lock
6
+ * Set rubocop.yml ruby value
7
+ * Use omakase rubocop settings
8
+ * Run rubocop on all files
9
+ * Add ENV['MT_NO_PLUGINS'] = '1' to Rakefile (and test helper?)
10
+ * Add better test reporter to test helper
11
+ * Add rake task for releasing
12
+ * Set gem version to start version value
13
+ * Set up git, perform initial commit
14
+ * Set up zeitwerk
15
+ * Set up bundle path and possibly use packaging
16
+ * Remove dead test
17
+ * Run rake to ensure everything works
18
+ * Release?
19
+
20
+
21
+
22
+
23
+ test helper plugin ignore:
24
+
25
+ ```ruby
26
+ unless ENV["RM_INFO"]
27
+ ENV["MT_NO_PLUGINS"] = "1"
28
+ Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)]
29
+ end
30
+ ```
31
+
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gempilot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - David Gillis
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: command_kit
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: warning
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: zeitwerk
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ email:
69
+ - david@flipmine.com
70
+ executables:
71
+ - gempilot
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".claude/skills/using-command-kit/SKILL.md"
76
+ - ".claude/skills/using-command-kit/cli-example.rb"
77
+ - ".claude/skills/using-command-kit/generator-pattern.rb"
78
+ - ".rspec"
79
+ - ".rubocop.yml"
80
+ - ".ruby-version"
81
+ - CLAUDE.md
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - data/templates/gem/Gemfile.erb
86
+ - data/templates/gem/LICENSE.txt.erb
87
+ - data/templates/gem/README.md.erb
88
+ - data/templates/gem/Rakefile.erb
89
+ - data/templates/gem/bin/console.erb
90
+ - data/templates/gem/bin/setup.erb
91
+ - data/templates/gem/dotfiles/github/workflows/ci.yml.erb
92
+ - data/templates/gem/dotfiles/gitignore
93
+ - data/templates/gem/dotfiles/rubocop.yml.erb
94
+ - data/templates/gem/dotfiles/ruby-version.erb
95
+ - data/templates/gem/exe/gem_name.erb
96
+ - data/templates/gem/gemspec.erb
97
+ - data/templates/gem/lib/gem_name.rb.erb
98
+ - data/templates/gem/lib/gem_name/version.rb.erb
99
+ - data/templates/gem/lib/gem_name_extension.rb.erb
100
+ - data/templates/gem/rspec.erb
101
+ - data/templates/gem/spec/gem_name_spec.rb.erb
102
+ - data/templates/gem/spec/spec_helper.rb.erb
103
+ - data/templates/gem/spec/zeitwerk_spec.rb.erb
104
+ - data/templates/gem/test/gem_name_test.rb.erb
105
+ - data/templates/gem/test/test_helper.rb.erb
106
+ - data/templates/gem/test/zeitwerk_test.rb.erb
107
+ - data/templates/new/.keep
108
+ - data/templates/new/command.rb.erb
109
+ - docs/command_kit_comparison.md
110
+ - docs/command_kit_reference.md
111
+ - docs/plans/2026-02-18-gempilot-add-command.md
112
+ - docs/superpowers/plans/2026-04-01-rubocop-new-config.md
113
+ - docs/superpowers/plans/2026-04-06-dogfood-inflectable.md
114
+ - docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md
115
+ - docs/superpowers/plans/2026-04-06-integrate-version-tools.md
116
+ - docs/superpowers/plans/2026-04-06-new-readme.md
117
+ - docs/version-management-redesign.md
118
+ - exe/gempilot
119
+ - issues.rec
120
+ - lib/core_ext/string/inflection_methods.rb
121
+ - lib/core_ext/string/refinements/inflectable.rb
122
+ - lib/gempilot.rb
123
+ - lib/gempilot/cli.rb
124
+ - lib/gempilot/cli/command.rb
125
+ - lib/gempilot/cli/commands/bump.rb
126
+ - lib/gempilot/cli/commands/console.rb
127
+ - lib/gempilot/cli/commands/create.rb
128
+ - lib/gempilot/cli/commands/destroy.rb
129
+ - lib/gempilot/cli/commands/new.rb
130
+ - lib/gempilot/cli/commands/release.rb
131
+ - lib/gempilot/cli/gem_builder.rb
132
+ - lib/gempilot/cli/gem_context.rb
133
+ - lib/gempilot/cli/generator.rb
134
+ - lib/gempilot/github_release.rb
135
+ - lib/gempilot/project.rb
136
+ - lib/gempilot/project/version.rb
137
+ - lib/gempilot/strict_shell.rb
138
+ - lib/gempilot/version.rb
139
+ - lib/gempilot/version_tag.rb
140
+ - lib/gempilot/version_task.rb
141
+ - notes.md
142
+ homepage: https://github.com/gillisd/gempilot
143
+ licenses:
144
+ - MIT
145
+ metadata:
146
+ homepage_uri: https://github.com/gillisd/gempilot
147
+ rubygems_mfa_required: 'true'
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '3.4'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubygems_version: 4.0.9
163
+ specification_version: 4
164
+ summary: A toolkit for creating, managing, and releasing your own rubygems
165
+ test_files: []