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
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
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.
|