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.
- checksums.yaml +7 -0
- data/.claude/skills/using-command-kit/SKILL.md +119 -0
- data/.claude/skills/using-command-kit/cli-example.rb +84 -0
- data/.claude/skills/using-command-kit/generator-pattern.rb +62 -0
- data/.rspec +3 -0
- data/.rubocop.yml +281 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +44 -0
- data/data/templates/gem/Gemfile.erb +23 -0
- data/data/templates/gem/LICENSE.txt.erb +21 -0
- data/data/templates/gem/README.md.erb +25 -0
- data/data/templates/gem/Rakefile.erb +36 -0
- data/data/templates/gem/bin/console.erb +7 -0
- data/data/templates/gem/bin/setup.erb +5 -0
- data/data/templates/gem/dotfiles/github/workflows/ci.yml.erb +33 -0
- data/data/templates/gem/dotfiles/gitignore +11 -0
- data/data/templates/gem/dotfiles/rubocop.yml.erb +209 -0
- data/data/templates/gem/dotfiles/ruby-version.erb +1 -0
- data/data/templates/gem/exe/gem_name.erb +3 -0
- data/data/templates/gem/gemspec.erb +27 -0
- data/data/templates/gem/lib/gem_name/version.rb.erb +7 -0
- data/data/templates/gem/lib/gem_name.rb.erb +16 -0
- data/data/templates/gem/lib/gem_name_extension.rb.erb +20 -0
- data/data/templates/gem/rspec.erb +3 -0
- data/data/templates/gem/spec/gem_name_spec.rb.erb +5 -0
- data/data/templates/gem/spec/spec_helper.rb.erb +10 -0
- data/data/templates/gem/spec/zeitwerk_spec.rb.erb +5 -0
- data/data/templates/gem/test/gem_name_test.rb.erb +7 -0
- data/data/templates/gem/test/test_helper.rb.erb +7 -0
- data/data/templates/gem/test/zeitwerk_test.rb.erb +9 -0
- data/data/templates/new/.keep +0 -0
- data/data/templates/new/command.rb.erb +15 -0
- data/docs/command_kit_comparison.md +249 -0
- data/docs/command_kit_reference.md +517 -0
- data/docs/plans/2026-02-18-gempilot-add-command.md +718 -0
- data/docs/superpowers/plans/2026-04-01-rubocop-new-config.md +838 -0
- data/docs/superpowers/plans/2026-04-06-dogfood-inflectable.md +659 -0
- data/docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md +166 -0
- data/docs/superpowers/plans/2026-04-06-integrate-version-tools.md +162 -0
- data/docs/superpowers/plans/2026-04-06-new-readme.md +185 -0
- data/docs/version-management-redesign.md +44 -0
- data/exe/gempilot +12 -0
- data/issues.rec +77 -0
- data/lib/core_ext/string/inflection_methods.rb +68 -0
- data/lib/core_ext/string/refinements/inflectable.rb +15 -0
- data/lib/gempilot/cli/command.rb +17 -0
- data/lib/gempilot/cli/commands/bump.rb +49 -0
- data/lib/gempilot/cli/commands/console.rb +38 -0
- data/lib/gempilot/cli/commands/create.rb +183 -0
- data/lib/gempilot/cli/commands/destroy.rb +136 -0
- data/lib/gempilot/cli/commands/new.rb +226 -0
- data/lib/gempilot/cli/commands/release.rb +40 -0
- data/lib/gempilot/cli/gem_builder.rb +105 -0
- data/lib/gempilot/cli/gem_context.rb +40 -0
- data/lib/gempilot/cli/generator.rb +90 -0
- data/lib/gempilot/cli.rb +19 -0
- data/lib/gempilot/github_release.rb +30 -0
- data/lib/gempilot/project/version.rb +39 -0
- data/lib/gempilot/project.rb +111 -0
- data/lib/gempilot/strict_shell.rb +18 -0
- data/lib/gempilot/version.rb +3 -0
- data/lib/gempilot/version_tag.rb +53 -0
- data/lib/gempilot/version_task.rb +108 -0
- data/lib/gempilot.rb +17 -0
- data/notes.md +31 -0
- metadata +165 -0
|
@@ -0,0 +1,718 @@
|
|
|
1
|
+
# `gempilot add` Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add a `gempilot add <type> <path>` command that generates classes, modules, and commands inside existing gems with proper Zeitwerk-compliant file paths, module nesting, and test files.
|
|
6
|
+
|
|
7
|
+
**Architecture:** The `add` command detects the gem context (name, test framework) from the current directory, parses the user-provided path into namespace segments, generates the source file with properly nested `module`/`class` wrappers, and creates a matching test file. No ERB templates for class/module (nesting depth is variable) — content is built programmatically. The `command` type uses an ERB template since its structure is fixed.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** CommandKit (options, arguments, interactive, colors, inflector), Generator module (mkdir, print_action), ERB for command template, Minitest for tests.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Key Design Decisions
|
|
14
|
+
|
|
15
|
+
**Why no ERB for class/module?** Nesting depth varies — `add class auth` is 1 level, `add class services/auth/token` is 3. Loops in ERB produce ugly templates. A Ruby method that builds the string is cleaner and testable.
|
|
16
|
+
|
|
17
|
+
**Why ERB for command?** Command boilerplate is fixed structure (always under `Gempilot::CLI::Commands`), so a template works well.
|
|
18
|
+
|
|
19
|
+
**Gem context detection:** Find `*.gemspec` in cwd. Extract gem name from filename. Detect test framework from `spec/` vs `test/` directory presence. No config file needed.
|
|
20
|
+
|
|
21
|
+
**Generator module reuse:** The `Add` command needs `mkdir` and `print_action` from Generator, but Generator requires `template_dir`. We'll set `template_dir` to `data/templates/add/` (for the command ERB) and add a `create_file(path, content)` helper to Generator for programmatic file creation.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Task 0: Create swap space
|
|
26
|
+
|
|
27
|
+
This environment has limited memory. Create 4GB of swap before doing anything else.
|
|
28
|
+
|
|
29
|
+
**Step 1: Create and enable swap**
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
sudo fallocate -l 4G /swapfile
|
|
33
|
+
sudo chmod 600 /swapfile
|
|
34
|
+
sudo mkswap /swapfile
|
|
35
|
+
sudo swapon /swapfile
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Step 2: Verify**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
free -h
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Expected: Swap line shows ~4.0G total.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Task 1: Add `create_file` helper to Generator
|
|
49
|
+
|
|
50
|
+
**Files:**
|
|
51
|
+
- Modify: `lib/gempilot/cli/generator.rb`
|
|
52
|
+
- Test: `test/gempilot/cli/generator_test.rb`
|
|
53
|
+
|
|
54
|
+
**Step 1: Write the failing test**
|
|
55
|
+
|
|
56
|
+
Add to `test/gempilot/cli/generator_test.rb`:
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
def test_create_file_writes_content
|
|
60
|
+
Dir.mktmpdir do |dir|
|
|
61
|
+
Dir.chdir(dir) do
|
|
62
|
+
path = "test_output.rb"
|
|
63
|
+
content = "# hello\n"
|
|
64
|
+
|
|
65
|
+
@generator.create_file(path, content)
|
|
66
|
+
|
|
67
|
+
assert_path_exists path
|
|
68
|
+
assert_equal content, File.read(path)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Note: Check the existing generator test file first to understand how `@generator` is set up. Adapt the test to match the existing pattern (it may use a test command class with `include Generator` and `template_dir` set). The key assertion is: `create_file` writes the content and the file exists.
|
|
75
|
+
|
|
76
|
+
**Step 2: Run test to verify it fails**
|
|
77
|
+
|
|
78
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/generator_test.rb`
|
|
79
|
+
Expected: FAIL — `NoMethodError: undefined method 'create_file'`
|
|
80
|
+
|
|
81
|
+
**Step 3: Write minimal implementation**
|
|
82
|
+
|
|
83
|
+
Add to `lib/gempilot/cli/generator.rb`, after the `touch` method:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
def create_file(path, content)
|
|
87
|
+
print_action "create", path
|
|
88
|
+
File.write(path, content)
|
|
89
|
+
end
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Step 4: Run test to verify it passes**
|
|
93
|
+
|
|
94
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/generator_test.rb`
|
|
95
|
+
Expected: PASS
|
|
96
|
+
|
|
97
|
+
**Step 5: Commit**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
git add lib/gempilot/cli/generator.rb test/gempilot/cli/generator_test.rb
|
|
101
|
+
git commit -m "feat(generator): add create_file helper for programmatic file creation"
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Task 2: Create the `add` command skeleton with gem context detection
|
|
107
|
+
|
|
108
|
+
**Files:**
|
|
109
|
+
- Create: `lib/gempilot/cli/commands/add.rb`
|
|
110
|
+
- Create: `test/gempilot/cli/add_command_test.rb`
|
|
111
|
+
|
|
112
|
+
**Step 1: Write the failing test**
|
|
113
|
+
|
|
114
|
+
Create `test/gempilot/cli/add_command_test.rb`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
require "test_helper"
|
|
118
|
+
require "gempilot/cli"
|
|
119
|
+
require "tmpdir"
|
|
120
|
+
require "stringio"
|
|
121
|
+
|
|
122
|
+
module Gempilot
|
|
123
|
+
class CLI
|
|
124
|
+
class AddCommandTest < Minitest::Test
|
|
125
|
+
def setup
|
|
126
|
+
@tmpdir = Dir.mktmpdir("add_command_test")
|
|
127
|
+
@original_dir = Dir.pwd
|
|
128
|
+
|
|
129
|
+
# Create a minimal gem directory to simulate being inside a gem
|
|
130
|
+
Dir.chdir(@tmpdir)
|
|
131
|
+
FileUtils.mkdir_p("lib/my_gem")
|
|
132
|
+
FileUtils.mkdir_p("test")
|
|
133
|
+
File.write("my_gem.gemspec", 'Gem::Specification.new { |s| s.name = "my_gem" }')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def teardown
|
|
137
|
+
Dir.chdir(@original_dir)
|
|
138
|
+
FileUtils.rm_rf(@tmpdir)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def test_add_class_creates_lib_file
|
|
142
|
+
run_add_command("class", "authentication")
|
|
143
|
+
|
|
144
|
+
assert_path_exists "lib/my_gem/authentication.rb"
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def test_add_class_creates_correct_module_nesting
|
|
148
|
+
run_add_command("class", "authentication")
|
|
149
|
+
|
|
150
|
+
content = File.read("lib/my_gem/authentication.rb")
|
|
151
|
+
|
|
152
|
+
assert_includes content, "module MyGem"
|
|
153
|
+
assert_includes content, "class Authentication"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
def run_add_command(type, path)
|
|
159
|
+
stdout = StringIO.new
|
|
160
|
+
command = Commands::Add.new(stdout: stdout)
|
|
161
|
+
command.main([type, path])
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Step 2: Run test to verify it fails**
|
|
169
|
+
|
|
170
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
171
|
+
Expected: FAIL — `NameError: uninitialized constant Commands::Add`
|
|
172
|
+
|
|
173
|
+
**Step 3: Write minimal implementation**
|
|
174
|
+
|
|
175
|
+
Create `data/templates/add/` directory (empty for now — needed by Generator):
|
|
176
|
+
```bash
|
|
177
|
+
mkdir -p /workspace/gempilot/misc-updates/data/templates/add
|
|
178
|
+
touch /workspace/gempilot/misc-updates/data/templates/add/.keep
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Create `lib/gempilot/cli/commands/add.rb`:
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
require_relative "../command"
|
|
185
|
+
require_relative "../generator"
|
|
186
|
+
require "command_kit/inflector"
|
|
187
|
+
|
|
188
|
+
module Gempilot
|
|
189
|
+
class CLI
|
|
190
|
+
module Commands
|
|
191
|
+
class Add < Command
|
|
192
|
+
include Generator
|
|
193
|
+
|
|
194
|
+
template_dir File.join(Gempilot::ROOT, "data", "templates", "add")
|
|
195
|
+
|
|
196
|
+
usage "[options] TYPE PATH"
|
|
197
|
+
description "Add a class, module, or command to an existing gem"
|
|
198
|
+
|
|
199
|
+
examples [
|
|
200
|
+
"class authentication",
|
|
201
|
+
"class services/authentication",
|
|
202
|
+
"module middleware",
|
|
203
|
+
"command deploy"
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
argument :type, required: true,
|
|
207
|
+
desc: "Type to generate (class, module, command)"
|
|
208
|
+
|
|
209
|
+
argument :path, required: true,
|
|
210
|
+
desc: "Path relative to gem namespace (e.g., services/authentication)"
|
|
211
|
+
|
|
212
|
+
def run(type = nil, path = nil)
|
|
213
|
+
type = type || begin
|
|
214
|
+
puts colors.bright_black("What kind of component do you want to add?")
|
|
215
|
+
ask_multiple_choice(colors.green("Type"), %w[class module command])
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
path = path || begin
|
|
219
|
+
puts
|
|
220
|
+
puts colors.bright_black("Path relative to the gem namespace (e.g., services/authentication).")
|
|
221
|
+
ask(colors.green("Path"), required: true)
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
detect_gem_context
|
|
225
|
+
|
|
226
|
+
case type
|
|
227
|
+
when "class" then add_class(path)
|
|
228
|
+
when "module" then add_module(path)
|
|
229
|
+
when "command" then add_command(path)
|
|
230
|
+
else
|
|
231
|
+
puts colors.red("Unknown type '#{type}'. Use class, module, or command.")
|
|
232
|
+
exit 1
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
def detect_gem_context
|
|
239
|
+
gemspec = Dir.glob("*.gemspec").first
|
|
240
|
+
|
|
241
|
+
unless gemspec
|
|
242
|
+
puts colors.red("No gemspec found in current directory. Run this from your gem's root.")
|
|
243
|
+
exit 1
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
@gem_name = File.basename(gemspec, ".gemspec")
|
|
247
|
+
@gem_module = CommandKit::Inflector.camelize(@gem_name)
|
|
248
|
+
@test_framework = File.directory?("spec") ? :rspec : :minitest
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def build_nested_source(namespaces, type_keyword, name)
|
|
252
|
+
lines = []
|
|
253
|
+
namespaces.each_with_index do |ns, i|
|
|
254
|
+
lines << "#{" " * i}module #{ns}"
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
depth = namespaces.length
|
|
258
|
+
lines << "#{" " * depth}#{type_keyword} #{name}"
|
|
259
|
+
lines << "#{" " * depth}end"
|
|
260
|
+
|
|
261
|
+
namespaces.length.times do |i|
|
|
262
|
+
lines << "#{" " * (namespaces.length - 1 - i)}end"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
lines.join("\n") + "\n"
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def parse_path(path)
|
|
269
|
+
segments = path.tr("-", "_").split("/")
|
|
270
|
+
modules = segments[0...-1].map { |s| CommandKit::Inflector.camelize(s) }
|
|
271
|
+
name = CommandKit::Inflector.camelize(segments.last)
|
|
272
|
+
[segments, modules, name]
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def add_class(path)
|
|
276
|
+
segments, intermediate_modules, class_name = parse_path(path)
|
|
277
|
+
namespaces = [@gem_module] + intermediate_modules
|
|
278
|
+
file_path = File.join("lib", @gem_name, *segments) + ".rb"
|
|
279
|
+
|
|
280
|
+
puts
|
|
281
|
+
puts colors.bright_white("Adding class ") + colors.bold(colors.cyan("#{namespaces.join('::')}::#{class_name}")) + colors.bright_white("...")
|
|
282
|
+
puts
|
|
283
|
+
|
|
284
|
+
# Create directories
|
|
285
|
+
dir = File.dirname(file_path)
|
|
286
|
+
mkdir(dir) unless File.directory?(dir)
|
|
287
|
+
|
|
288
|
+
# Create lib file
|
|
289
|
+
source = "# frozen_string_literal: true\n\n" + build_nested_source(namespaces, "class", class_name)
|
|
290
|
+
create_file(file_path, source)
|
|
291
|
+
|
|
292
|
+
# Create test file
|
|
293
|
+
add_test_file(namespaces, class_name, segments)
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def add_module(path)
|
|
297
|
+
segments, intermediate_modules, mod_name = parse_path(path)
|
|
298
|
+
namespaces = [@gem_module] + intermediate_modules
|
|
299
|
+
file_path = File.join("lib", @gem_name, *segments) + ".rb"
|
|
300
|
+
|
|
301
|
+
puts
|
|
302
|
+
puts colors.bright_white("Adding module ") + colors.bold(colors.cyan("#{namespaces.join('::')}::#{mod_name}")) + colors.bright_white("...")
|
|
303
|
+
puts
|
|
304
|
+
|
|
305
|
+
dir = File.dirname(file_path)
|
|
306
|
+
mkdir(dir) unless File.directory?(dir)
|
|
307
|
+
|
|
308
|
+
source = "# frozen_string_literal: true\n\n" + build_nested_source(namespaces, "module", mod_name)
|
|
309
|
+
create_file(file_path, source)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def add_command(path)
|
|
313
|
+
segments, _, command_name = parse_path(path)
|
|
314
|
+
file_path = File.join("lib", @gem_name, "cli", "commands", segments.last + ".rb")
|
|
315
|
+
|
|
316
|
+
puts
|
|
317
|
+
puts colors.bright_white("Adding command ") + colors.bold(colors.cyan(command_name)) + colors.bright_white("...")
|
|
318
|
+
puts
|
|
319
|
+
|
|
320
|
+
dir = File.dirname(file_path)
|
|
321
|
+
mkdir(dir) unless File.directory?(dir)
|
|
322
|
+
|
|
323
|
+
@command_name = command_name
|
|
324
|
+
@command_file_name = segments.last
|
|
325
|
+
erb "command.rb.erb", file_path
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def add_test_file(namespaces, class_name, segments)
|
|
329
|
+
if @test_framework == :rspec
|
|
330
|
+
test_path = File.join("spec", @gem_name, *segments) + "_spec.rb"
|
|
331
|
+
test_dir = File.dirname(test_path)
|
|
332
|
+
mkdir(test_dir) unless File.directory?(test_dir)
|
|
333
|
+
|
|
334
|
+
content = <<~RUBY
|
|
335
|
+
# frozen_string_literal: true
|
|
336
|
+
|
|
337
|
+
require "spec_helper"
|
|
338
|
+
|
|
339
|
+
RSpec.describe #{namespaces.join('::')}::#{class_name} do
|
|
340
|
+
pending "add some examples"
|
|
341
|
+
end
|
|
342
|
+
RUBY
|
|
343
|
+
create_file(test_path, content)
|
|
344
|
+
else
|
|
345
|
+
test_path = File.join("test", @gem_name, *segments) + "_test.rb"
|
|
346
|
+
test_dir = File.dirname(test_path)
|
|
347
|
+
mkdir(test_dir) unless File.directory?(test_dir)
|
|
348
|
+
|
|
349
|
+
content = <<~RUBY
|
|
350
|
+
# frozen_string_literal: true
|
|
351
|
+
|
|
352
|
+
require "test_helper"
|
|
353
|
+
|
|
354
|
+
module #{namespaces.first}
|
|
355
|
+
class #{(namespaces[1..] + [class_name]).join('::')}Test < Minitest::Test
|
|
356
|
+
def test_placeholder
|
|
357
|
+
assert true
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
RUBY
|
|
362
|
+
create_file(test_path, content)
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
**Step 4: Run test to verify it passes**
|
|
372
|
+
|
|
373
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
374
|
+
Expected: PASS
|
|
375
|
+
|
|
376
|
+
**Step 5: Commit**
|
|
377
|
+
|
|
378
|
+
```bash
|
|
379
|
+
git add lib/gempilot/cli/commands/add.rb test/gempilot/cli/add_command_test.rb data/templates/add/.keep
|
|
380
|
+
git commit -m "feat: add 'gempilot add' command with class generation"
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
### Task 3: Add tests for nested paths and module nesting
|
|
386
|
+
|
|
387
|
+
**Files:**
|
|
388
|
+
- Modify: `test/gempilot/cli/add_command_test.rb`
|
|
389
|
+
|
|
390
|
+
**Step 1: Write the failing tests**
|
|
391
|
+
|
|
392
|
+
Add to the test class:
|
|
393
|
+
|
|
394
|
+
```ruby
|
|
395
|
+
def test_add_class_with_nested_path_creates_directories
|
|
396
|
+
run_add_command("class", "services/authentication")
|
|
397
|
+
|
|
398
|
+
assert File.directory?("lib/my_gem/services")
|
|
399
|
+
assert_path_exists "lib/my_gem/services/authentication.rb"
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
def test_add_class_with_nested_path_has_correct_nesting
|
|
403
|
+
run_add_command("class", "services/authentication")
|
|
404
|
+
|
|
405
|
+
content = File.read("lib/my_gem/services/authentication.rb")
|
|
406
|
+
|
|
407
|
+
assert_includes content, "module MyGem"
|
|
408
|
+
assert_includes content, "module Services"
|
|
409
|
+
assert_includes content, "class Authentication"
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def test_add_class_with_deeply_nested_path
|
|
413
|
+
run_add_command("class", "services/auth/token_validator")
|
|
414
|
+
|
|
415
|
+
content = File.read("lib/my_gem/services/auth/token_validator.rb")
|
|
416
|
+
|
|
417
|
+
assert_includes content, "module MyGem"
|
|
418
|
+
assert_includes content, "module Services"
|
|
419
|
+
assert_includes content, "module Auth"
|
|
420
|
+
assert_includes content, "class TokenValidator"
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def test_add_class_creates_frozen_string_literal
|
|
424
|
+
run_add_command("class", "authentication")
|
|
425
|
+
|
|
426
|
+
content = File.read("lib/my_gem/authentication.rb")
|
|
427
|
+
|
|
428
|
+
assert content.start_with?("# frozen_string_literal: true")
|
|
429
|
+
end
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Step 2: Run tests to verify they pass**
|
|
433
|
+
|
|
434
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
435
|
+
Expected: PASS (implementation from Task 2 should handle these)
|
|
436
|
+
|
|
437
|
+
If any fail, fix the implementation and re-run.
|
|
438
|
+
|
|
439
|
+
**Step 3: Commit**
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
git add test/gempilot/cli/add_command_test.rb
|
|
443
|
+
git commit -m "test: add nested path tests for gempilot add class"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
### Task 4: Add tests for test file generation
|
|
449
|
+
|
|
450
|
+
**Files:**
|
|
451
|
+
- Modify: `test/gempilot/cli/add_command_test.rb`
|
|
452
|
+
|
|
453
|
+
**Step 1: Write the failing tests**
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
def test_add_class_creates_minitest_file
|
|
457
|
+
run_add_command("class", "authentication")
|
|
458
|
+
|
|
459
|
+
assert_path_exists "test/my_gem/authentication_test.rb"
|
|
460
|
+
|
|
461
|
+
content = File.read("test/my_gem/authentication_test.rb")
|
|
462
|
+
|
|
463
|
+
assert_includes content, "require \"test_helper\""
|
|
464
|
+
assert_includes content, "module MyGem"
|
|
465
|
+
assert_includes content, "Minitest::Test"
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def test_add_class_creates_rspec_file_when_spec_dir_exists
|
|
469
|
+
# Switch to rspec by creating spec/ and removing test/
|
|
470
|
+
FileUtils.rm_rf("test")
|
|
471
|
+
FileUtils.mkdir_p("spec")
|
|
472
|
+
|
|
473
|
+
run_add_command("class", "authentication")
|
|
474
|
+
|
|
475
|
+
assert_path_exists "spec/my_gem/authentication_spec.rb"
|
|
476
|
+
|
|
477
|
+
content = File.read("spec/my_gem/authentication_spec.rb")
|
|
478
|
+
|
|
479
|
+
assert_includes content, "require \"spec_helper\""
|
|
480
|
+
assert_includes content, "RSpec.describe MyGem::Authentication"
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def test_add_class_creates_nested_test_file
|
|
484
|
+
run_add_command("class", "services/authentication")
|
|
485
|
+
|
|
486
|
+
assert_path_exists "test/my_gem/services/authentication_test.rb"
|
|
487
|
+
end
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
**Step 2: Run tests**
|
|
491
|
+
|
|
492
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
493
|
+
Expected: PASS
|
|
494
|
+
|
|
495
|
+
**Step 3: Commit**
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
git add test/gempilot/cli/add_command_test.rb
|
|
499
|
+
git commit -m "test: add test file generation tests for gempilot add"
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
### Task 5: Add module generation and tests
|
|
505
|
+
|
|
506
|
+
**Files:**
|
|
507
|
+
- Modify: `test/gempilot/cli/add_command_test.rb`
|
|
508
|
+
|
|
509
|
+
**Step 1: Write the failing tests**
|
|
510
|
+
|
|
511
|
+
```ruby
|
|
512
|
+
def test_add_module_creates_lib_file
|
|
513
|
+
run_add_command("module", "middleware")
|
|
514
|
+
|
|
515
|
+
assert_path_exists "lib/my_gem/middleware.rb"
|
|
516
|
+
|
|
517
|
+
content = File.read("lib/my_gem/middleware.rb")
|
|
518
|
+
|
|
519
|
+
assert_includes content, "module MyGem"
|
|
520
|
+
assert_includes content, "module Middleware"
|
|
521
|
+
refute_includes content, "class"
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def test_add_module_does_not_create_test_file
|
|
525
|
+
run_add_command("module", "middleware")
|
|
526
|
+
|
|
527
|
+
refute_path_exists "test/my_gem/middleware_test.rb"
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
def test_add_module_with_nested_path
|
|
531
|
+
run_add_command("module", "services/concerns")
|
|
532
|
+
|
|
533
|
+
content = File.read("lib/my_gem/services/concerns.rb")
|
|
534
|
+
|
|
535
|
+
assert_includes content, "module MyGem"
|
|
536
|
+
assert_includes content, "module Services"
|
|
537
|
+
assert_includes content, "module Concerns"
|
|
538
|
+
end
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Step 2: Run tests**
|
|
542
|
+
|
|
543
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
544
|
+
Expected: PASS (implementation from Task 2 covers modules)
|
|
545
|
+
|
|
546
|
+
**Step 3: Commit**
|
|
547
|
+
|
|
548
|
+
```bash
|
|
549
|
+
git add test/gempilot/cli/add_command_test.rb
|
|
550
|
+
git commit -m "test: add module generation tests for gempilot add"
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
### Task 6: Add command generation with ERB template
|
|
556
|
+
|
|
557
|
+
**Files:**
|
|
558
|
+
- Create: `data/templates/add/command.rb.erb`
|
|
559
|
+
- Modify: `test/gempilot/cli/add_command_test.rb`
|
|
560
|
+
|
|
561
|
+
**Step 1: Write the failing test**
|
|
562
|
+
|
|
563
|
+
```ruby
|
|
564
|
+
def test_add_command_creates_command_file
|
|
565
|
+
# Need cli/commands directory to exist (simulates a CLI gem)
|
|
566
|
+
FileUtils.mkdir_p("lib/my_gem/cli/commands")
|
|
567
|
+
|
|
568
|
+
run_add_command("command", "deploy")
|
|
569
|
+
|
|
570
|
+
assert_path_exists "lib/my_gem/cli/commands/deploy.rb"
|
|
571
|
+
|
|
572
|
+
content = File.read("lib/my_gem/cli/commands/deploy.rb")
|
|
573
|
+
|
|
574
|
+
assert_includes content, "class Deploy < Command"
|
|
575
|
+
assert_includes content, "module MyGem"
|
|
576
|
+
assert_includes content, "module Commands"
|
|
577
|
+
assert_includes content, 'description'
|
|
578
|
+
end
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
**Step 2: Run test to verify it fails**
|
|
582
|
+
|
|
583
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
584
|
+
Expected: FAIL — template file missing, `Errno::ENOENT`
|
|
585
|
+
|
|
586
|
+
**Step 3: Create the ERB template**
|
|
587
|
+
|
|
588
|
+
Create `data/templates/add/command.rb.erb`:
|
|
589
|
+
|
|
590
|
+
```erb
|
|
591
|
+
# frozen_string_literal: true
|
|
592
|
+
|
|
593
|
+
require_relative "../command"
|
|
594
|
+
|
|
595
|
+
module <%= @gem_module %>
|
|
596
|
+
class CLI
|
|
597
|
+
module Commands
|
|
598
|
+
class <%= @command_name %> < Command
|
|
599
|
+
usage "[options]"
|
|
600
|
+
description "TODO: describe the <%= @command_file_name %> command"
|
|
601
|
+
|
|
602
|
+
def run
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
end
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
**Step 4: Run test to verify it passes**
|
|
611
|
+
|
|
612
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
613
|
+
Expected: PASS
|
|
614
|
+
|
|
615
|
+
**Step 5: Commit**
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
git add data/templates/add/command.rb.erb test/gempilot/cli/add_command_test.rb
|
|
619
|
+
git commit -m "feat: add command generation with ERB template"
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
---
|
|
623
|
+
|
|
624
|
+
### Task 7: Add error handling test
|
|
625
|
+
|
|
626
|
+
**Files:**
|
|
627
|
+
- Modify: `test/gempilot/cli/add_command_test.rb`
|
|
628
|
+
|
|
629
|
+
**Step 1: Write the failing test**
|
|
630
|
+
|
|
631
|
+
```ruby
|
|
632
|
+
def test_add_fails_without_gemspec
|
|
633
|
+
# Remove gemspec
|
|
634
|
+
FileUtils.rm("my_gem.gemspec")
|
|
635
|
+
|
|
636
|
+
stdout = StringIO.new
|
|
637
|
+
command = Commands::Add.new(stdout: stdout)
|
|
638
|
+
|
|
639
|
+
assert_raises(SystemExit) do
|
|
640
|
+
command.main(["class", "foo"])
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def test_add_fails_with_unknown_type
|
|
645
|
+
stdout = StringIO.new
|
|
646
|
+
command = Commands::Add.new(stdout: stdout)
|
|
647
|
+
|
|
648
|
+
assert_raises(SystemExit) do
|
|
649
|
+
command.main(["widget", "foo"])
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
**Step 2: Run tests**
|
|
655
|
+
|
|
656
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest test/gempilot/cli/add_command_test.rb`
|
|
657
|
+
Expected: PASS
|
|
658
|
+
|
|
659
|
+
**Step 3: Commit**
|
|
660
|
+
|
|
661
|
+
```bash
|
|
662
|
+
git add test/gempilot/cli/add_command_test.rb
|
|
663
|
+
git commit -m "test: add error handling tests for gempilot add"
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
---
|
|
667
|
+
|
|
668
|
+
### Task 8: Run full test suite and verify
|
|
669
|
+
|
|
670
|
+
**Step 1: Run all tests**
|
|
671
|
+
|
|
672
|
+
Run: `cd /workspace/gempilot/misc-updates && ruby -Ilib -Itest -e 'Dir.glob("test/**/*_test.rb").each { |f| require_relative f }'`
|
|
673
|
+
Expected: All tests pass (30 existing + new add command tests)
|
|
674
|
+
|
|
675
|
+
**Step 2: Manual smoke test**
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
cd /tmp
|
|
679
|
+
ruby -I/workspace/gempilot/misc-updates/lib -e 'require "gempilot/cli"; Gempilot::CLI.start' -- new smoke_test --test minitest --no-exe --no-git --author Test --email t@t.com --summary Test --ruby-version 3.4.8
|
|
680
|
+
cd smoke_test
|
|
681
|
+
ruby -I/workspace/gempilot/misc-updates/lib -e 'require "gempilot/cli"; Gempilot::CLI.start' -- add class services/authentication
|
|
682
|
+
ruby -I/workspace/gempilot/misc-updates/lib -e 'require "gempilot/cli"; Gempilot::CLI.start' -- add module middleware
|
|
683
|
+
cat lib/smoke_test/services/authentication.rb
|
|
684
|
+
cat lib/smoke_test/middleware.rb
|
|
685
|
+
cat test/smoke_test/services/authentication_test.rb
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Expected output for `lib/smoke_test/services/authentication.rb`:
|
|
689
|
+
```ruby
|
|
690
|
+
# frozen_string_literal: true
|
|
691
|
+
|
|
692
|
+
module SmokeTest
|
|
693
|
+
module Services
|
|
694
|
+
class Authentication
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
end
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Step 3: Commit (if any fixes needed)**
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
git add -A
|
|
704
|
+
git commit -m "fix: address issues found in smoke test"
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
## Summary of files
|
|
710
|
+
|
|
711
|
+
| File | Action |
|
|
712
|
+
|------|--------|
|
|
713
|
+
| `lib/gempilot/cli/generator.rb` | Add `create_file` method |
|
|
714
|
+
| `lib/gempilot/cli/commands/add.rb` | Create — the Add command |
|
|
715
|
+
| `data/templates/add/command.rb.erb` | Create — ERB template for command type |
|
|
716
|
+
| `data/templates/add/.keep` | Create — keeps directory in git |
|
|
717
|
+
| `test/gempilot/cli/add_command_test.rb` | Create — comprehensive tests |
|
|
718
|
+
| `test/gempilot/cli/generator_test.rb` | Modify — test for `create_file` |
|