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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2af205d18b1882cd54aef57cc2196eecd566dc4e960d8aa03935859dadb94845
4
+ data.tar.gz: c412a708efa730b78865b98b70cda6a8b968430b31015edc93034acba7cb0dc6
5
+ SHA512:
6
+ metadata.gz: a58637783d6a69c9dc5cb2c85220612959a47a234fd8e392ed4006311c9ce1b31d4534339a6c55df05066c91eba56bd013ef600f988e2865481b8ac1e82c6d35
7
+ data.tar.gz: adc65d18871b31d1853b4f1c705dcb5d2efd24ecbab16d6d5010131c5d32ad7158e05ea356e4fa4ab7166a0a47b7484f64be711954465a87efe84727d03cadb0
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: using-command-kit
3
+ description: Use when building Ruby CLI applications with command_kit or command_mapper gems, structuring subcommands, adding options/arguments, generating files from ERB templates, or prompting users for input in Ruby
4
+ ---
5
+
6
+ # Building Ruby CLIs with CommandKit + CommandMapper
7
+
8
+ CommandKit is a modular Ruby toolkit for building CLI commands with composable include-based modules. CommandMapper wraps external CLI tools with typed Ruby DSLs. Reference implementation: [Ronin](https://github.com/ronin-rb/ronin).
9
+
10
+ **Core principle:** Include only what you need. Never hand-roll what CommandKit already provides.
11
+
12
+ ## Quick Reference
13
+
14
+ | Need | Module | Key Method |
15
+ |------|--------|------------|
16
+ | Subcommand dispatch | `Commands` | `command(name, klass)` |
17
+ | Auto-discover commands | `Commands::AutoLoad` | `AutoLoad.new(dir:, namespace:)` |
18
+ | Options | `Options` | `option :name, value: {type:}, desc:` |
19
+ | Arguments | `Arguments` | `argument :name, required:, desc:` |
20
+ | Prompt user | `Interactive` | `ask`, `ask_yes_or_no`, `ask_multiple_choice` |
21
+ | Colored output | `Colors` | `colors.green("text")` |
22
+ | ERB templates | `FileUtils` | `erb(source, dest)` |
23
+ | String inflection | `Inflector` | `camelize`, `underscore`, `demodularize` |
24
+ | Version flag | `Options::Version` | `version "1.0.0"` |
25
+ | Bug reports | `BugReport` | `bug_report_url "https://..."` |
26
+ | Tables | `Printing::Tables` | `print_table(rows, header:, border:)` |
27
+ | Pager | `Pager` | `pager { |io| io.puts data }` |
28
+
29
+ All modules prefixed `CommandKit::`.
30
+
31
+ ## CLI Structure
32
+
33
+ ```
34
+ exe/mytool # Thin entry (see BUNDLE_GEMFILE note below)
35
+ lib/mytool/cli.rb # Commands + AutoLoad
36
+ lib/mytool/cli/command.rb # Base command (Colors, Interactive, BugReport)
37
+ lib/mytool/cli/commands/foo.rb # Auto-discovered as "mytool foo"
38
+ data/templates/ # ERB templates for generators
39
+ ```
40
+
41
+ **exe entry point must set `BUNDLE_GEMFILE`** so it works from any directory:
42
+ ```ruby
43
+ #!/usr/bin/env ruby
44
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
45
+ require "bundler/setup"
46
+ require "mytool/cli"
47
+ MyTool::CLI.start
48
+ ```
49
+
50
+ Without this, `require "bundler/setup"` fails with `Bundler::GemfileNotFound` when called from outside the project directory.
51
+
52
+ See `cli-example.rb` for complete CLI + base command + subcommand implementation.
53
+
54
+ ## Generator Pattern
55
+
56
+ For file generation commands, build a Generator mixin with `template_dir`, `mkdir`, `erb`, `cp`, `sh` wrappers that print colored actions. See `generator-pattern.rb` for full implementation following Ronin's pattern.
57
+
58
+ Templates use instance variables set in `run()` via ERB binding:
59
+ ```erb
60
+ module <%= @module_name %>
61
+ VERSION = "0.1.0"
62
+ end
63
+ ```
64
+
65
+ ## Boolean Options with Negation
66
+
67
+ CommandKit does NOT auto-generate `--no-<name>` flags. Use OptionParser's `--[no-]` syntax via `long:`:
68
+
69
+ ```ruby
70
+ # ❌ Only creates --verbose (no way to explicitly disable)
71
+ option :verbose, desc: "Enable verbose output"
72
+
73
+ # ✅ Creates both --verbose and --no-verbose
74
+ option :verbose, long: '--[no-]verbose', desc: "Enable verbose output"
75
+ ```
76
+
77
+ With `--[no-]`, OptionParser passes `true` for `--verbose`, `false` for `--no-verbose`. When neither is passed, the key is absent from `options`. Use `options.has_key?(:verbose)` to detect "not provided" vs explicit choice.
78
+
79
+ ## Interactive Prompting
80
+
81
+ Include `Interactive` in base Command. Prompt when args are missing:
82
+ ```ruby
83
+ def run(name = nil)
84
+ @name = name || ask("Name:", required: true)
85
+ @test = options[:test] || ask_multiple_choice("Framework:", %w[minitest rspec])
86
+ @git = options.fetch(:git) { ask_yes_or_no("Init git?", default: true) }
87
+ end
88
+ ```
89
+
90
+ **`ask_multiple_choice` return values:**
91
+ - Array form: `ask_multiple_choice("Pick:", %w[minitest rspec])` → returns `String` (`"minitest"`)
92
+ - Hash form: `ask_multiple_choice("Pick:", {"minitest" => :minitest, "rspec" => :rspec})` → returns the **value** (`:minitest`), not the key
93
+
94
+ Use the Hash form when you need symbols or mapped values.
95
+
96
+ **Rich prompts** — print colored explanation before `ask()`:
97
+ ```ruby
98
+ puts colors.bright_black("A short (one line) summary of what your gem does.")
99
+ ask(colors.green("Summary"), default: "A new Ruby gem")
100
+ ```
101
+
102
+ Also: `ask_secret(prompt)`, `ask_multiline(prompt, terminator:)`.
103
+
104
+ ## CommandMapper
105
+
106
+ Use `command_mapper` gem for typed interfaces to external CLI tools (bundler, git, etc.). Define subcommands, options with types, and arguments. Execute with `run_command`, `capture_command`, or inspect with `command_string`.
107
+
108
+ ## Common Mistakes
109
+
110
+ | Mistake | Fix |
111
+ |---------|-----|
112
+ | Hand-rolling inflection | `Inflector.camelize(name)` |
113
+ | No `examples` on commands | Always define `examples [...]` |
114
+ | Not prompting for missing args | Include `Interactive`, prompt in `run` |
115
+ | Multiple shell execution paths | Pick one: `system()`, `Open3`, or CommandMapper |
116
+ | Forgetting `bug_report_url` | One line in base Command, inherited everywhere |
117
+ | Skipping `description` | Always set - drives help and command summary |
118
+ | Expecting `--no-flag` to work | Must use `long: '--[no-]flag'` explicitly |
119
+ | Bare `require "bundler/setup"` in exe | Set `ENV["BUNDLE_GEMFILE"]` first |
@@ -0,0 +1,84 @@
1
+ # Complete CommandKit CLI example following Ronin patterns
2
+ #
3
+ # Entry point (exe/mytool):
4
+ # #!/usr/bin/env ruby
5
+ # ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
6
+ # require "bundler/setup"
7
+ # require "mytool/cli"
8
+ # MyTool::CLI.start
9
+
10
+ # lib/mytool/cli.rb
11
+ require "command_kit/commands"
12
+ require "command_kit/commands/auto_load"
13
+ require "command_kit/options/version"
14
+
15
+ module MyTool
16
+ class CLI
17
+ include CommandKit::Commands
18
+ include CommandKit::Commands::AutoLoad.new(
19
+ dir: "#{__dir__}/cli/commands",
20
+ namespace: "#{self}::Commands"
21
+ )
22
+ include CommandKit::Options::Version
23
+
24
+ command_name "mytool"
25
+ version MyTool::VERSION
26
+
27
+ command_aliases["g"] = "generate"
28
+ end
29
+ end
30
+
31
+ # lib/mytool/cli/command.rb
32
+ module MyTool
33
+ class CLI
34
+ class Command < CommandKit::Command
35
+ include CommandKit::Colors
36
+ include CommandKit::Interactive
37
+
38
+ bug_report_url "https://github.com/user/mytool/issues/new"
39
+ end
40
+ end
41
+ end
42
+
43
+ # lib/mytool/cli/commands/generate.rb
44
+ module MyTool
45
+ class CLI
46
+ module Commands
47
+ class Generate < Command
48
+ include Generator
49
+
50
+ template_dir File.join(ROOT, "data", "templates", "component")
51
+
52
+ usage "[options] NAME"
53
+ description "Generate a new component"
54
+
55
+ examples [
56
+ "my_widget",
57
+ "--test rspec my_widget",
58
+ "--no-git my_widget"
59
+ ]
60
+
61
+ option :test, value: {
62
+ type: { "minitest" => :minitest, "rspec" => :rspec },
63
+ default: :minitest
64
+ }, desc: "Test framework"
65
+
66
+ option :git, desc: "Initialize git repo"
67
+
68
+ argument :name, required: true, desc: "Component name"
69
+
70
+ # Prompt for missing args via CommandKit::Interactive
71
+ def run(name = nil)
72
+ @name = name || ask("Component name:", required: true)
73
+ @module_name = CommandKit::Inflector.camelize(@name)
74
+ @test = options[:test] || ask_multiple_choice("Test framework:", %w[minitest rspec])
75
+ @git = options.fetch(:git) { ask_yes_or_no("Initialize git?", default: true) }
76
+
77
+ mkdir @name
78
+ erb "main.rb.erb", "#{@name}/lib/#{@name}.rb"
79
+ erb "version.rb.erb", "#{@name}/lib/#{@name}/version.rb"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,62 @@
1
+ # Generator mixin following ronin-core's Core::CLI::Generator pattern.
2
+ # Provides template_dir, mkdir, erb, cp, sh wrappers with colored output.
3
+ # See: ronin-core/lib/ronin/core/cli/generator.rb
4
+
5
+ module MyTool
6
+ class CLI
7
+ module Generator
8
+ include CommandKit::Colors
9
+ include CommandKit::FileUtils
10
+
11
+ def self.included(command)
12
+ command.extend ClassMethods
13
+ end
14
+
15
+ module ClassMethods
16
+ def template_dir(path = nil)
17
+ if path
18
+ @template_dir = File.expand_path(path)
19
+ else
20
+ @template_dir || (superclass.template_dir if superclass.respond_to?(:template_dir))
21
+ end
22
+ end
23
+ end
24
+
25
+ attr_reader :template_dir
26
+
27
+ def initialize(**kwargs)
28
+ super
29
+ unless (@template_dir = self.class.template_dir)
30
+ raise NotImplementedError, "#{self.class} did not define template_dir"
31
+ end
32
+ end
33
+
34
+ def print_action(action, source = nil, dest)
35
+ line = "\t#{colors.bold(colors.green(action))}"
36
+ line << "\t#{colors.green(source)}" if source
37
+ line << "\t#{colors.green(dest)}" if dest
38
+ puts line
39
+ end
40
+
41
+ def mkdir(path)
42
+ print_action "mkdir", path
43
+ ::FileUtils.mkdir_p(path)
44
+ end
45
+
46
+ def cp(source, dest)
47
+ print_action "cp", source, dest
48
+ ::FileUtils.cp(File.join(@template_dir, source), dest)
49
+ end
50
+
51
+ def erb(source, dest = nil)
52
+ print_action("erb", source, dest) if dest
53
+ super(File.join(@template_dir, source), dest)
54
+ end
55
+
56
+ def sh(command, *arguments)
57
+ print_action "run", [command, *arguments].join(" ")
58
+ system(command, *arguments)
59
+ end
60
+ end
61
+ end
62
+ end
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,281 @@
1
+ # ===========================================================================
2
+ # RuboCop Configuration
3
+ #
4
+ # Base: Stock RuboCop defaults
5
+ # AI guardrails: rubocop-claude plugin (all Claude/ cops + stricter metrics)
6
+ # Performance: rubocop-performance (with chain-hostile cops disabled)
7
+ #
8
+ # Philosophy: idiomatic Ruby, pipeline-style chaining, strict for AI agents,
9
+ # readable for humans.
10
+ # ===========================================================================
11
+
12
+ plugins:
13
+ - rubocop-claude
14
+ - rubocop-minitest
15
+ - rubocop-performance
16
+ - rubocop-rake
17
+ - rubocop-rspec
18
+
19
+ AllCops:
20
+ NewCops: enable
21
+ TargetRubyVersion: 3.4
22
+ Exclude:
23
+ - bin/*
24
+ - vendor/**/*
25
+ - data/templates/**/*
26
+ - references/**/*
27
+
28
+ # ===========================================================================
29
+ # Overrides from stock — personal style preferences
30
+ # ===========================================================================
31
+
32
+ # Double quotes everywhere. One less decision to make.
33
+ Style/StringLiterals:
34
+ EnforcedStyle: double_quotes
35
+
36
+ Style/StringLiteralsInInterpolation:
37
+ EnforcedStyle: double_quotes
38
+
39
+ # Frozen string literal is transitional cruft. Ruby 3.4 has chilled strings,
40
+ # full default freeze is coming in a future Ruby.
41
+ Style/FrozenStringLiteralComment:
42
+ EnforcedStyle: never
43
+
44
+ # Pipeline style. Chaining multi-line blocks is the whole point.
45
+ Style/MultilineBlockChain:
46
+ Enabled: false
47
+
48
+ # Block delimiters are a taste call. Pipeline code uses braces for chaining,
49
+ # do/end for side effects. No cop captures this nuance.
50
+ Style/BlockDelimiters:
51
+ Enabled: false
52
+
53
+ # Write arrays like arrays.
54
+ Style/WordArray:
55
+ Enabled: false
56
+
57
+ Style/SymbolArray:
58
+ Enabled: false
59
+
60
+ # Argument indentation: consistent 2-space indent, not aligned to first arg.
61
+ Layout/FirstArgumentIndentation:
62
+ EnforcedStyle: consistent
63
+
64
+ # Dot-aligned chaining. Dots form a visual column.
65
+ Layout/MultilineMethodCallIndentation:
66
+ EnforcedStyle: aligned
67
+
68
+ # ===========================================================================
69
+ # Overrides from rubocop-claude — loosen where pipeline style conflicts
70
+ # ===========================================================================
71
+
72
+ Claude/NoOverlyDefensiveCode:
73
+ MaxSafeNavigationChain: 2
74
+
75
+ Style/SafeNavigation:
76
+ MaxChainLength: 2
77
+
78
+ # Allow `return a, b` for tuple-style returns.
79
+ Style/RedundantReturn:
80
+ AllowMultipleReturnValues: true
81
+
82
+ # ===========================================================================
83
+ # Overrides from rubocop-performance — disable chain-hostile cops
84
+ # ===========================================================================
85
+
86
+ Performance/ChainArrayAllocation:
87
+ Enabled: false
88
+
89
+ Performance/MapMethodChain:
90
+ Enabled: false
91
+
92
+ # ===========================================================================
93
+ # Additional tightening — not set by stock or rubocop-claude
94
+ # ===========================================================================
95
+
96
+ # Zeitwerk loaders are mutable by design — only flag literal mutable values.
97
+ Style/MutableConstant:
98
+ EnforcedStyle: literals
99
+
100
+ # Short blocks push toward small chained steps instead of fat lambdas.
101
+ Metrics/BlockLength:
102
+ Max: 8
103
+ CountAsOne:
104
+ - array
105
+ - hash
106
+ - heredoc
107
+ - method_call
108
+ AllowedMethods:
109
+ - command
110
+ Exclude:
111
+ - "test/**/*"
112
+ - "gempilot.gemspec"
113
+ - "rakelib/**/*.rake"
114
+
115
+ Metrics/ClassLength:
116
+ Exclude:
117
+ - "test/**/*"
118
+
119
+ Metrics/MethodLength:
120
+ Exclude:
121
+ - "test/**/*"
122
+
123
+ Metrics/AbcSize:
124
+ Exclude:
125
+ - "test/**/*"
126
+
127
+ Claude/NoFancyUnicode:
128
+ Exclude:
129
+ - "gempilot.gemspec"
130
+
131
+ # Minitest tests legitimately define many assertions per test method
132
+ # when testing multi-file generators. Default is 3, too tight.
133
+ Minitest/MultipleAssertions:
134
+ Max: 10
135
+
136
+ # Anonymous forwarding (*, **, &) breaks TruffleRuby, JRuby, and
137
+ # Ruby < 3.2. Named args are explicit and portable.
138
+ #
139
+ # # bad — anonymous forwarding, not portable
140
+ # def process(*, **, &)
141
+ # other_method(*, **, &)
142
+ # end
143
+ #
144
+ # # good — named, works everywhere, readable
145
+ # def process(*args, **kwargs, &block)
146
+ # other_method(*args, **kwargs, &block)
147
+ # end
148
+ Style/ArgumentsForwarding:
149
+ Enabled: false
150
+
151
+ # Explicit begin/rescue/end is clearer than implicit method-body rescue.
152
+ # The begin block scopes what's being rescued. Without it, the rescue
153
+ # looks like it belongs to the method signature.
154
+ #
155
+ # # bad — what exactly is being rescued here?
156
+ # def process
157
+ # logger.info("starting")
158
+ # result = dangerous_call
159
+ # logger.info("done")
160
+ # rescue NetworkError => e
161
+ # retry_later(e)
162
+ # end
163
+ #
164
+ # # good — begin scopes the danger
165
+ # def process
166
+ # logger.info("starting")
167
+ # begin
168
+ # result = dangerous_call
169
+ # rescue NetworkError => e
170
+ # retry_later(e)
171
+ # end
172
+ # logger.info("done")
173
+ # end
174
+ Style/RedundantBegin:
175
+ Enabled: false
176
+
177
+ # Classes get rdoc. Run `rake rdoc` and keep it honest.
178
+ #
179
+ # # bad
180
+ # class UserService
181
+ # def call(user) = process(user)
182
+ # end
183
+ #
184
+ # # good
185
+ # # Handles user lifecycle operations including activation,
186
+ # # deactivation, and profile updates.
187
+ # class UserService
188
+ # def call(user) = process(user)
189
+ # end
190
+ Style/Documentation:
191
+ Enabled: true
192
+ Exclude:
193
+ - "spec/**/*"
194
+ - "test/**/*"
195
+
196
+ # Trailing commas in multiline literals and arguments.
197
+ # Cleaner diffs — adding an element doesn't touch the previous line.
198
+ # Also saves keystrokes when appending.
199
+ #
200
+ # # bad
201
+ # method_call(
202
+ # arg1,
203
+ # arg2
204
+ # )
205
+ #
206
+ # # good
207
+ # method_call(
208
+ # arg1,
209
+ # arg2,
210
+ # )
211
+ #
212
+ # # bad
213
+ # hash = {
214
+ # name: "Alice",
215
+ # age: 30
216
+ # }
217
+ #
218
+ # # good
219
+ # hash = {
220
+ # name: "Alice",
221
+ # age: 30,
222
+ # }
223
+ Style/TrailingCommaInArrayLiteral:
224
+ EnforcedStyleForMultiline: comma
225
+
226
+ Style/TrailingCommaInHashLiteral:
227
+ EnforcedStyleForMultiline: comma
228
+
229
+ Style/TrailingCommaInArguments:
230
+ EnforcedStyleForMultiline: comma
231
+
232
+ # ===========================================================================
233
+ # RSpec — rubocop-rspec overrides
234
+ # ===========================================================================
235
+
236
+ # Not every describe block wraps a class. Feature specs, request specs,
237
+ # and integration tests describe behavior, not objects.
238
+ #
239
+ # # "bad" according to this cop — but perfectly valid
240
+ # RSpec.describe "User registration flow" do
241
+ # it "sends a welcome email" do
242
+ # ...
243
+ # end
244
+ # end
245
+ #
246
+ # # this cop only wants
247
+ # RSpec.describe User do
248
+ # ...
249
+ # end
250
+ RSpec/DescribeClass:
251
+ Enabled: false
252
+
253
+ # Subject placement is a readability call, not a rule. Sometimes
254
+ # let blocks need to come first to make subject comprehensible.
255
+ #
256
+ # # "bad" according to this cop
257
+ # let(:user) { create(:user) }
258
+ # subject { described_class.new(user) }
259
+ #
260
+ # # cop wants subject first — but then you're reading
261
+ # # `described_class.new(user)` before knowing what `user` is
262
+ RSpec/LeadingSubject:
263
+ Enabled: false
264
+
265
+ # Block style for expect { }.to change { } reads like a sentence
266
+ # and makes the mutation scope explicit.
267
+ #
268
+ # # bad (method style)
269
+ # expect { user.activate! }.to change(user, :active?).from(false).to(true)
270
+ #
271
+ # # good (block style)
272
+ # expect { user.activate! }.to change { user.active? }.from(false).to(true)
273
+ RSpec/ExpectChange:
274
+ EnforcedStyle: block
275
+
276
+ RSpec/NamedSubject:
277
+ Enabled: false
278
+
279
+ # have_file_content matcher accepts a filename string in expect()
280
+ RSpec/ExpectActual:
281
+ Enabled: false
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.4.8
data/CLAUDE.md ADDED
@@ -0,0 +1,45 @@
1
+ ## Gempilot
2
+
3
+ A CLI tool for creating and managing Ruby gems, built on CommandKit.
4
+
5
+ ### Terminology
6
+ - **"the repo"** — this gempilot codebase
7
+ - **"gem repos"** — gems generated by gempilot (output of `gempilot create`)
8
+
9
+ ### Commands
10
+ - `gempilot create` — Scaffold a new gem (templates in `data/templates/gem/`)
11
+ - `gempilot new` — Generate a class, module, or command in an existing gem (templates in `data/templates/new/`)
12
+ - `gempilot destroy` — Remove a class, module, or command from an existing gem
13
+ - `gempilot bump` — Bump version in `version.rb` (patch default, or minor/major)
14
+ - `gempilot release` — Thin proxy to `rake release`
15
+ - `gempilot console` — Thin proxy to `bin/console`
16
+
17
+ ### Architecture
18
+ - Uses Zeitwerk for autoloading
19
+ - Uses CommandKit for CLI framework (commands, options, arguments, interactive prompts)
20
+ - Uses CommandKit::Inflector for name inflection
21
+ - Generator module (`lib/gempilot/cli/generator.rb`) provides template rendering via ERB
22
+ - GemContext module (`lib/gempilot/cli/gem_context.rb`) shared by new, destroy, release, console
23
+ - CommandKit::Commands::AutoLoad maps filenames in `commands/` to command names
24
+
25
+ ### Testing
26
+ - Minitest with minitest-reporters
27
+ - Run tests: `bundle exec rake test`
28
+
29
+ ### Generated Gem Features
30
+ - Zeitwerk autoloading with `LOADER` constant exposed for eager loading
31
+ - Zeitwerk validation test (`test/zeitwerk_test.rb` or `spec/zeitwerk_spec.rb`)
32
+ - `rake zeitwerk:validate` task to verify naming conventions
33
+ - Choice of minitest or rspec
34
+ - RuboCop with framework-specific plugins
35
+ - GitHub Actions CI workflow (`.github/workflows/ci.yml`)
36
+ - `git ls-files`-based gemspec with glob fallback for non-git repos
37
+ - Version management rake tasks in `rakelib/version.rake` (current, bump, commit, revert, release:full)
38
+
39
+ ### Notes
40
+ - AutoLoad uses block form in `cli.rb` with explicit `summary:` per command (lazy loading means descriptions aren't available at help time without this)
41
+
42
+
43
+ ## ISSUES
44
+
45
+ 1. RESOLVED — `exe/gempilot` had unconditional `ENV["BUNDLE_GEMFILE"]` and `require "bundler/setup"`, causing `Gemfile not found` after `gem install`. Removed both lines; RubyGems handles load paths for installed gems. (Ronin avoids this with a conditional `Gemfile.lock` check, but gempilot's `exe/`+`bin/` separation makes that unnecessary.)
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 David Gillis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.