gempilot 0.2.2 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 48657265841f0000324b7cfca38d98ed1a45a59e7906810660a8e9e047042286
4
- data.tar.gz: 9a084c85bfeec3ea094da9e7b7b245a0e30a050631db84acf8d434e88470b948
3
+ metadata.gz: b4b895f3da5d0d8866cd13f835dd3c1ca71d8d433571c2d7531735636eeb6ad0
4
+ data.tar.gz: 878dad31a22bdecdb9ff607ebecf2330432070219edfc3e08b66a5022c205d77
5
5
  SHA512:
6
- metadata.gz: 677fa36cce5bf93834debebf366296289b5fe69baf1ecb325d2169aa03140e928373a36052d4e4accfff37aebdfdb5562bbdda09244c5e6e15b1f51b982397c9
7
- data.tar.gz: 8afb56703f50f6f42c56b7049167c498656576c8c4e5061cdd11883b9d32efe0ea7906203391dc809d4197c222f654010439fd38ead128ce9783446dafa50e54
6
+ metadata.gz: ccfc9410fe3ccd3cc8f49e3ca34f68ef334a50183eb95d0788f09c9a5e972fcb4b1244cdfeee188b9898e25ba1c4542d6c4cf24ee24e54edebd4e8b8e907f6a1
7
+ data.tar.gz: e7deb74172aa7bcd75d84f41ad2d80022d9befcd7f1b69990c6f8b9fe6002168d8b25581242dbded279bdd601986c24552b1431573ff0ed62bec8316734e50fe
data/.rubocop.yml CHANGED
@@ -15,10 +15,11 @@ plugins:
15
15
  - rubocop-performance
16
16
  - rubocop-rake
17
17
  - rubocop-rspec
18
+ - rubocop-design
18
19
 
19
20
  AllCops:
20
21
  NewCops: enable
21
- TargetRubyVersion: 3.4
22
+ TargetRubyVersion: 4.0
22
23
  Exclude:
23
24
  - bin/*
24
25
  - vendor/**/*
@@ -279,3 +280,17 @@ RSpec/NamedSubject:
279
280
  # have_file_content matcher accepts a filename string in expect()
280
281
  RSpec/ExpectActual:
281
282
  Enabled: false
283
+
284
+ Design/PositionalArguments:
285
+ Enabled: false
286
+
287
+ Design/KeywordArguments:
288
+ Enabled: false
289
+
290
+ Design/TotalArguments:
291
+ Enabled: false
292
+
293
+ Design/AgentNounClassName:
294
+ Exclude:
295
+ - 'lib/gempilot/generator.rb'
296
+ - 'test/gempilot/cli/generator_test.rb'
data/CLAUDE.md CHANGED
@@ -36,11 +36,3 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
36
36
  - GitHub Actions CI workflow (`.github/workflows/ci.yml`)
37
37
  - `git ls-files`-based gemspec with glob fallback for non-git repos
38
38
  - Version lifecycle rake tasks installed via `Gempilot::VersionTask.new` (a `Rake::TaskLib`): `version:current/bump/commit/tag/untag/reset/revert`, composite `version:release`/`version:unrelease`, and `version:github:release/unrelease/list`
39
-
40
- ### Notes
41
- - AutoLoad uses block form in `cli.rb` with explicit `summary:` per command (lazy loading means descriptions aren't available at help time without this)
42
-
43
-
44
- ## ISSUES
45
-
46
- 1. RESOLVED — `exe/gempilot` previously had an unconditional `ENV["BUNDLE_GEMFILE"]` + `require "bundler/setup"`, breaking `gem install` usage. It now guards both behind `if File.exist?(gemfile)` (the Ronin pattern), so installed gems skip Bundler while in-repo runs still use it.
data/issues.rec CHANGED
@@ -26,7 +26,7 @@ Id: F0833E66-A35C-4054-884B-F90C81F08838
26
26
  Updated: Mon, 02 Mar 2026 10:37:33 -0500
27
27
  Title: Error handling should be graceful
28
28
  Description: I did "gempilot create", and entered "A foo" as the title, and it crashed horrifically. This particular example should have validation at the minimum. Need to also use command kit's error bubbling/handling mechanism to swallow errors and print message to user. Can always use verbose or debug mode to see full trace.
29
- Status: open
29
+ Status: closed
30
30
 
31
31
  Id: 741FFD05-9BD7-44EB-BB99-E7D705F12681
32
32
  Updated: Mon, 02 Mar 2026 11:25:24 -0500
@@ -68,7 +68,7 @@ Id: 74D0FD90-0421-4452-8591-2798EE6FD23E
68
68
  Updated: Sat, 28 Mar 2026 12:59:40 -0400
69
69
  Title: Publish gem
70
70
  Description: At some point the repo should be moved to public viz, and the gem should be published.
71
- Status: open
71
+ Status: closed
72
72
 
73
73
  Id: 8C356024-3122-11F1-89F3-FE6CB9572C2F
74
74
  Updated: Mon, 06 Apr 2026 00:00:00 -0400
@@ -141,3 +141,30 @@ Description: "
141
141
  + ```
142
142
  + "
143
143
  Status: closed
144
+
145
+ Id: 1C46F71C-70E9-11F1-B65B-FE6CB9572C2E
146
+ Updated: Thu, 25 Jun 2026 18:56:32 -0400
147
+ Title: Incorrectly creates test file
148
+ Description: When doing `gempilot new Reversal::Server`, in a project called "reversal-store", I get:
149
+ +
150
+ + gempilot new class Reversal::Server
151
+ +
152
+ + Adding class Reversal::Server...
153
+ +
154
+ + create lib/reversal/server.rb
155
+ + create test/reversal/store_test.rb
156
+ +
157
+ + The test file is incorrectly named. Should be server_test.rb
158
+ Status: open
159
+
160
+ Id: F5486B48-71DB-11F1-980F-FE6CB9572C2E
161
+ Updated: Fri, 26 Jun 2026 23:54:55 -0400
162
+ Title: Rubymine <=> Minitest usage causes "no reporters allowed error"
163
+ Description: Has to do with how the ENV var RM_INFO is being interpreted
164
+ Status: open
165
+
166
+ Id: 8D847572-7273-11F1-B508-FE6CB9572C2E
167
+ Updated: Sat, 27 Jun 2026 18:00:04 -0400
168
+ Title: Gem extensions are broken
169
+ Description: when using gempilot on a gem extension, like 'foo-support', using idiomatic ruby patterns for dir structure, gempilot Project class throws an error
170
+ Status: open
@@ -27,9 +27,8 @@ module Gempilot
27
27
  end
28
28
 
29
29
  def print_console_banner
30
- puts colors.bright_white("Starting console for ") +
31
- colors.bold(colors.cyan(@gem_name)) +
32
- colors.bright_white("...")
30
+ styled_name = colors.bold(colors.cyan(@gem_name))
31
+ puts colors.bright_white("Starting console for ") + styled_name + colors.bright_white("...")
33
32
  puts
34
33
  end
35
34
  end
@@ -156,7 +156,8 @@ module Gempilot
156
156
 
157
157
  puts
158
158
  puts colors.bright_black("An executable in exe/ lets users run your gem from the command line.")
159
- options[:exe] = ask_yes_or_no(colors.green("Create an executable"), default: false)
159
+ prompt = colors.green("Create an executable")
160
+ options[:exe] = ask_yes_or_no(prompt, default: false)
160
161
  end
161
162
 
162
163
  def collect_git_options
@@ -169,7 +170,8 @@ module Gempilot
169
170
 
170
171
  puts
171
172
  puts colors.bright_black("Initialize a git repository with an initial commit.")
172
- options[:git] = ask_yes_or_no(colors.green("Initialize git repo"), default: true)
173
+ prompt = colors.green("Initialize git repo")
174
+ options[:git] = ask_yes_or_no(prompt, default: true)
173
175
  end
174
176
 
175
177
  def collect_branch
@@ -181,8 +183,9 @@ module Gempilot
181
183
  end
182
184
 
183
185
  def print_header
186
+ styled_name = colors.bold(colors.cyan(@gem_name))
184
187
  puts
185
- puts colors.bright_white("Creating gem '") + colors.bold(colors.cyan(@gem_name)) + colors.bright_white("'...")
188
+ puts colors.bright_white("Creating gem '") + styled_name + colors.bright_white("'...")
186
189
  puts
187
190
  end
188
191
 
@@ -77,10 +77,9 @@ module Gempilot
77
77
  end
78
78
 
79
79
  def print_adding_banner(kind, label)
80
+ styled_label = colors.bold(colors.cyan(label))
80
81
  puts
81
- puts colors.bright_white("Adding #{kind} ") +
82
- colors.bold(colors.cyan(label)) +
83
- colors.bright_white("...")
82
+ puts colors.bright_white("Adding #{kind} ") + styled_label + colors.bright_white("...")
84
83
  puts
85
84
  end
86
85
 
@@ -18,9 +18,8 @@ module Gempilot
18
18
  private
19
19
 
20
20
  def print_release_banner
21
- puts colors.bright_white("Releasing ") +
22
- colors.bold(colors.cyan(@gem_name)) +
23
- colors.bright_white("...")
21
+ styled_name = colors.bold(colors.cyan(@gem_name))
22
+ puts colors.bright_white("Releasing ") + styled_name + colors.bright_white("...")
24
23
  puts
25
24
  end
26
25
 
@@ -37,8 +37,9 @@ module Gempilot
37
37
  end
38
38
 
39
39
  def print_action(command, dest, source: nil)
40
+ styled_command = colors.bold(colors.green(command))
40
41
  line = +""
41
- line << "\t" << colors.bold(colors.green(command))
42
+ line << "\t" << styled_command
42
43
  line << "\t" << colors.green(source) if source
43
44
  line << "\t" << colors.green(dest) if dest
44
45
 
@@ -1,8 +1,9 @@
1
- require "pathname"
2
1
  require "warning"
3
2
 
4
3
  module Gempilot
5
4
  ## Introspects a gem project to discover its name, module, and version.
5
+ ## Works for both regular gems (+lib/my_gem.rb+) and extension gems whose
6
+ ## entry point nests deeper (+lib/my_gem/extension.rb+ for +my_gem-extension+).
6
7
  class Project
7
8
  class ProjectIntrospectionError < StandardError; end
8
9
 
@@ -29,11 +30,11 @@ module Gempilot
29
30
  end
30
31
 
31
32
  def name
32
- lib_project.basename.to_s
33
+ project_segments.join("-")
33
34
  end
34
35
 
35
36
  def klass
36
- Object.const_get(name.camelize)
37
+ Object.const_get(project_segments.map(&:camelize).join("::"))
37
38
  end
38
39
 
39
40
  def version
@@ -67,6 +68,10 @@ module Gempilot
67
68
 
68
69
  private
69
70
 
71
+ def project_segments
72
+ lib_project.relative_path_from(lib).each_filename.to_a
73
+ end
74
+
70
75
  def with_version_file
71
76
  version.path.open(File::RDWR, 0o644) do |f|
72
77
  f.flock File::LOCK_EX
@@ -76,8 +81,7 @@ module Gempilot
76
81
  end
77
82
 
78
83
  def fetch_lib_project
79
- files = lib.glob("*.rb")
80
- dirs = files.map { it.sub_ext("") }.select(&:directory?)
84
+ dirs = shallowest_entry_dirs
81
85
  case dirs.count
82
86
  in 0 then raise ProjectIntrospectionError, "Could not identify project dir"
83
87
  in (2..)
@@ -87,6 +91,22 @@ module Gempilot
87
91
  end
88
92
  end
89
93
 
94
+ def shallowest_entry_dirs
95
+ entry_dirs.group_by { depth_below_lib(it) }
96
+ .min_by(&:first)
97
+ &.last || []
98
+ end
99
+
100
+ def entry_dirs
101
+ lib.glob("**/*.rb")
102
+ .map { it.sub_ext("") }
103
+ .select(&:directory?)
104
+ end
105
+
106
+ def depth_below_lib(path)
107
+ path.relative_path_from(lib).each_filename.count
108
+ end
109
+
90
110
  def fetch_version
91
111
  Warning.ignore(REDEFINITION_WARNING)
92
112
  Warning.ignore(REINITIALIZATION_WARNING)
@@ -1,3 +1,3 @@
1
1
  module Gempilot
2
- VERSION = "0.2.2".freeze
2
+ VERSION = "0.2.3".freeze
3
3
  end
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gempilot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Gillis
@@ -106,16 +106,6 @@ files:
106
106
  - data/templates/gem/test/zeitwerk_test.rb.erb
107
107
  - data/templates/new/.keep
108
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/superpowers/plans/2026-06-09-address-review-critiques.md
118
- - docs/version-management-redesign.md
119
109
  - exe/gempilot
120
110
  - issues.rec
121
111
  - lib/core_ext/string/inflection_methods.rb
@@ -140,7 +130,7 @@ files:
140
130
  - lib/gempilot/version.rb
141
131
  - lib/gempilot/version_tag.rb
142
132
  - lib/gempilot/version_task.rb
143
- - notes.md
133
+ - vendor/vendored.gemv
144
134
  homepage: https://github.com/gillisd/gempilot
145
135
  licenses:
146
136
  - MIT
@@ -154,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
154
144
  requirements:
155
145
  - - ">="
156
146
  - !ruby/object:Gem::Version
157
- version: '3.4'
147
+ version: '4.0'
158
148
  required_rubygems_version: !ruby/object:Gem::Requirement
159
149
  requirements:
160
150
  - - ">="
161
151
  - !ruby/object:Gem::Version
162
152
  version: '0'
163
153
  requirements: []
164
- rubygems_version: 4.0.14
154
+ rubygems_version: 4.0.15
165
155
  specification_version: 4
166
156
  summary: A toolkit for creating, managing, and releasing your own rubygems
167
157
  test_files: []
@@ -1,249 +0,0 @@
1
- # Gempilot vs Ronin: CommandKit Usage Comparison
2
-
3
- ## How Gempilot Uses CommandKit
4
-
5
- ### Current Usage (after restructuring)
6
-
7
- | CommandKit Module | Used? | Where |
8
- |---|---|---|
9
- | `CommandKit::Commands` | Yes | `Gempilot::CLI` - top-level command dispatcher |
10
- | `CommandKit::Commands::AutoLoad` | Yes | `Gempilot::CLI` - auto-discovers commands from `cli/commands/` |
11
- | `CommandKit::Options::Version` | Yes | `Gempilot::CLI` - `--version` flag |
12
- | `CommandKit::Command` | Yes | `Gempilot::CLI::Command` - base class for all commands |
13
- | `CommandKit::Colors` | Yes | `Gempilot::CLI::Command` and `Gempilot::CLI::Generator` |
14
- | `CommandKit::FileUtils` | Yes | `Gempilot::CLI::Generator` - `erb()` method |
15
- | `CommandKit::Options` | Yes | `Commands::New` - all option definitions (implicit via Command) |
16
- | `CommandKit::Arguments` | Yes | `Commands::New` - `gem_name` argument (implicit via Command) |
17
- | `CommandKit::Inflector` | **No** | Hand-rolled `inflect_module` in `Commands::New` instead |
18
- | `CommandKit::Interactive` | **No** | Not included in base Command |
19
- | `CommandKit::Description` | Yes | `Commands::New` - `description "Create a new gem"` |
20
- | `CommandKit::Usage` | Yes | `Commands::New` - `usage "[options] GEM_NAME"` |
21
- | `CommandKit::Printing` | Yes | Implicit via Command |
22
- | `CommandKit::Printing::Indent` | **No** | |
23
- | `CommandKit::Printing::Fields` | **No** | |
24
- | `CommandKit::Printing::Lists` | **No** | |
25
- | `CommandKit::Printing::Tables` | **No** | |
26
- | `CommandKit::Terminal` | **No** | |
27
- | `CommandKit::Pager` | **No** | |
28
- | `CommandKit::OS` | **No** | |
29
- | `CommandKit::XDG` | **No** | |
30
- | `CommandKit::BugReport` | **No** | |
31
- | `CommandKit::Help::Man` | **No** | |
32
- | `CommandKit::Edit` | **No** | |
33
- | `CommandKit::Examples` | **No** | No examples defined on any command |
34
- | `CommandKit::ExceptionHandler` | Yes | Implicit via Command |
35
-
36
- ### What Gempilot Hand-Rolls Instead of Using CommandKit
37
-
38
- 1. **Inflector** - `Commands::New#inflect_module` (line 113-115) does:
39
- ```ruby
40
- def inflect_module(name)
41
- name.split(/[-_]/).map(&:capitalize).join
42
- end
43
- ```
44
- Should use `CommandKit::Inflector.camelize(name)` which handles the same transformation plus edge cases (e.g., `/` to `::` for nested modules).
45
-
46
- 2. **Generator module** - `Gempilot::CLI::Generator` reimplements ronin-core's Generator pattern. This is intentional (own implementation, no ronin-core dependency), but it duplicates logic that `CommandKit::FileUtils` already partly provides (`erb` method). The Generator adds `print_action`, `mkdir`, `cp`, `sh`, `touch`, `chmod` wrappers with colored output.
47
-
48
- 3. **Shell execution** - `Gempilot::Commands#sh` uses a custom `Runner` class with PTY/Open3 pipeline for command execution. The Generator module has its own simpler `sh` that calls `system()`. Two different shell execution paths exist.
49
-
50
- ---
51
-
52
- ## How Ronin Uses CommandKit (Reference Implementation)
53
-
54
- ### Module Usage
55
-
56
- | CommandKit Module | Used? | Where |
57
- |---|---|---|
58
- | `CommandKit::Commands` | Yes | `Ronin::CLI` |
59
- | `CommandKit::Commands::AutoLoad` | Yes | `Ronin::CLI`, `Commands::New`, and many others |
60
- | `CommandKit::Options::Version` | Yes | `Ronin::CLI` |
61
- | `CommandKit::Command` | Yes (via ronin-core) | `Core::CLI::Command` base class |
62
- | `CommandKit::Colors` | Yes | `Core::CLI::Generator`, syntax highlighting |
63
- | `CommandKit::FileUtils` | Yes | `Core::CLI::Generator` |
64
- | `CommandKit::Options` | Yes | Every command |
65
- | `CommandKit::Arguments` | Yes | Every command |
66
- | `CommandKit::Interactive` | **No** | Not used in ronin itself |
67
- | `CommandKit::Description` | Yes | Every command |
68
- | `CommandKit::Usage` | Yes | Every command |
69
- | `CommandKit::Examples` | Yes | Most commands have examples |
70
- | `CommandKit::Printing` | Yes | Implicit via Command |
71
- | `CommandKit::Printing::Indent` | Yes | Output formatting |
72
- | `CommandKit::Printing::Tables` | Yes | Database query output |
73
- | `CommandKit::Terminal` | Yes | Width-aware output |
74
- | `CommandKit::Pager` | Yes | Long output paging |
75
- | `CommandKit::OS` | Yes | OS-specific behavior |
76
- | `CommandKit::BugReport` | Yes | `Core::CLI::Command` base class |
77
- | `CommandKit::Help::Man` | Yes | Every command has `man_page` |
78
- | `CommandKit::Inflector` | Yes | Used internally for command name derivation |
79
-
80
- ### Ronin Patterns Worth Adopting
81
-
82
- 1. **Every command has `examples`** - Ronin defines usage examples on almost every command:
83
- ```ruby
84
- examples [
85
- 'project foo',
86
- 'script foo.rb'
87
- ]
88
- ```
89
-
90
- 2. **`bug_report_url` on base command** - Set once, inherited everywhere:
91
- ```ruby
92
- class Command < Core::CLI::Command
93
- bug_report_url 'https://github.com/ronin-rb/ronin/issues/new'
94
- end
95
- ```
96
-
97
- 3. **Man pages** - Every command declares `man_page 'ronin-command.1'` for full documentation.
98
-
99
- 4. **Nested AutoLoad** - The `new` command is itself a Commands container with AutoLoad:
100
- ```ruby
101
- class New < Command
102
- include CommandKit::Commands::AutoLoad.new(
103
- dir: "#{__dir__}/new",
104
- namespace: "#{self}"
105
- )
106
- end
107
- ```
108
- This allows `ronin new project`, `ronin new script`, etc. Each subcommand is a separate file in `commands/new/`.
109
-
110
- 5. **Core::CLI::Generator** builds on CommandKit::FileUtils - It adds `template_dir`, `print_action`, and wrappers for `mkdir`, `cp`, `erb`, `chmod`, `sh`. Gempilot's Generator follows this pattern.
111
-
112
- 6. **Consistent use of Inflector** - Ronin never hand-rolls string case conversion.
113
-
114
- ---
115
-
116
- ## Side-by-Side: CLI Entry Points
117
-
118
- ### Gempilot
119
- ```ruby
120
- # lib/gempilot/cli.rb
121
- class Gempilot::CLI
122
- include CommandKit::Commands
123
- include CommandKit::Commands::AutoLoad.new(
124
- dir: "#{__dir__}/cli/commands",
125
- namespace: "#{self}::Commands"
126
- )
127
- include CommandKit::Options::Version
128
-
129
- command_name "gempilot"
130
- version Gempilot::VERSION
131
- end
132
- ```
133
-
134
- ### Ronin
135
- ```ruby
136
- # lib/ronin/cli.rb
137
- class Ronin::CLI
138
- include CommandKit::Commands
139
- include CommandKit::Commands::AutoLoad.new(
140
- dir: "#{__dir__}/cli/commands",
141
- namespace: "#{self}::Commands"
142
- )
143
- include CommandKit::Options::Version
144
- include Core::CLI::Help::Banner
145
-
146
- command_name 'ronin'
147
- version Ronin::VERSION
148
-
149
- command_aliases['enc'] = 'encode'
150
- command_aliases['dec'] = 'decode'
151
- end
152
- ```
153
-
154
- **Verdict:** Nearly identical. Gempilot follows the pattern correctly. Ronin adds `command_aliases` and a help banner, both nice touches.
155
-
156
- ---
157
-
158
- ## Side-by-Side: Generator Commands
159
-
160
- ### Gempilot - `Commands::New`
161
- ```ruby
162
- class New < Command
163
- include Generator
164
-
165
- template_dir File.join(Gempilot::ROOT, "data", "templates", "gem")
166
-
167
- option :summary, value: { type: String }, desc: "Gem summary"
168
- option :test, value: { type: { "minitest" => :minitest, "rspec" => :rspec }, default: :minitest }, desc: "Test framework"
169
- argument :gem_name, required: true, desc: "Name of the gem"
170
-
171
- def run(gem_name)
172
- @gem_name = gem_name
173
- @module_name = inflect_module(gem_name)
174
- mkdir gem_name
175
- erb "gemspec.erb", "#{gem_name}/#{gem_name}.gemspec"
176
- # ...
177
- end
178
- end
179
- ```
180
-
181
- ### Ronin - `Commands::New::Project`
182
- ```ruby
183
- class Project < Command
184
- include Core::CLI::Generator
185
-
186
- template_dir File.join(ROOT, 'data', 'templates', 'project')
187
-
188
- option :git, desc: 'Initializes a git repo'
189
- option :ruby_version, value: { type: String, usage: 'VERSION', default: RUBY_VERSION }, desc: '...'
190
- argument :path, required: true, desc: 'The directory to create'
191
-
192
- description 'Creates a new Ruby project directory'
193
- man_page 'ronin-new-project.1'
194
-
195
- def run(path)
196
- @project_name = File.basename(path)
197
- @ruby_version = options[:ruby_version]
198
- mkdir path
199
- erb 'Gemfile.erb', File.join(path, 'Gemfile')
200
- # ...
201
- end
202
- end
203
- ```
204
-
205
- **Verdict:** Same pattern. Gempilot's version is correct. Ronin adds `man_page` declaration and uses `usage:` on option values for better help output.
206
-
207
- ---
208
-
209
- ## Grading: Gempilot's Idiomatic Use of CommandKit
210
-
211
- ### Grade: **B+**
212
-
213
- Gempilot's restructured codebase follows the command_kit patterns well. The CLI entry, base command, AutoLoad, option/argument definitions, and Generator module all match the Ronin reference closely.
214
-
215
- ### What Earns the Grade
216
-
217
- **Done right:**
218
- - CLI with `Commands` + `AutoLoad` - textbook implementation
219
- - Base `Command` class with `Colors` - follows the pattern
220
- - Generator module following ronin-core's design
221
- - Option definitions with `value:` Hash, proper types
222
- - Argument definitions with `required: true`
223
- - `template_dir` class method pattern
224
- - `description` and `usage` on commands
225
- - ERB templates with instance variable binding
226
- - Executable entry point: thin `CLI.start`
227
-
228
- **Holding it back from A:**
229
-
230
- 1. **`CommandKit::Inflector` not used** - Hand-rolled `inflect_module` instead of `Inflector.camelize`. This is specifically called out in CLAUDE.md issue #2.
231
-
232
- 2. **No `CommandKit::Interactive`** - CLAUDE.md issue #1 asks for interactive prompting when arguments are missing. `CommandKit::Interactive` provides `ask`, `ask_yes_or_no`, `ask_multiple_choice` - exactly what's needed.
233
-
234
- 3. **No `examples` on any command** - Ronin defines examples on every command. Gempilot defines none. Easy win for help output.
235
-
236
- 4. **No `bug_report_url`** - Low effort, high value for user experience.
237
-
238
- 5. **Two shell execution paths** - `Generator#sh` (uses `system`) and `Commands#sh` / `Runner` (uses Open3 pipeline with PTY). Should converge.
239
-
240
- 6. **`CommandMapper` integration unclear** - `CommandMappers::Bundle` is a 1091-line DSL that's not wired into any command yet. The `Commands::New#run` calls `sh 'bundle', 'install'` directly instead of using the typed DSL.
241
-
242
- ### What Would Make It an A
243
-
244
- 1. Replace `inflect_module` with `CommandKit::Inflector.camelize`
245
- 2. Include `CommandKit::Interactive` in base Command, use it for missing-argument prompting
246
- 3. Add `examples` to `Commands::New`
247
- 4. Add `bug_report_url` to base Command
248
- 5. Wire `CommandMappers::Bundle` into the `new` command for `bundle install` / `bundle config` operations
249
- 6. Add `CommandKit::Printing::Indent` for structured output during generation