gempilot 0.2.4 → 0.3.1

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: ed510959d35011586d23fc0dcc0baff3a453a2acbd1a2d1c8fb4489cee63e6e0
4
- data.tar.gz: 455ba876df7b3fb40df26932737cae4062a6e6e2210f1fd02b18a231566bd23d
3
+ metadata.gz: 686fd797469009843d44529b0831634baf163cbd007a7d625f9605e340aa021a
4
+ data.tar.gz: f1144a8db6d9a54e8b4cfb146aae7d4d240c92f87a08cf4501589241934cfcbc
5
5
  SHA512:
6
- metadata.gz: 4ce5185ea5c07adc72435dee47b2f749f4f6146f26a09c8d8c557490e4be1b69b2b048e4c9ab32b8548d043c9a0d393cc32a0d8dd23c94c98cabd3a8737334d9
7
- data.tar.gz: 6094227a45e9bb173f0455dc155bd3ac7593698a7c41c7ac1482c5e7c44a59a90c547bb20206d3c7887b87a84977fc1f7ca615b2975fe53dc6a8ab85384a83e8
6
+ metadata.gz: 5ae0fdd86a89ec6855ae589c6c64fc28d97e71df117f81a35440ca9029333d962171daf080c8d0b24adafe2e83a4e41e2cb0bd989399d8df9cc79dfb2da6d9b0
7
+ data.tar.gz: abcea8380c013d64f9f22d8c82a59ffbb18a24ff5bcd0be017c0a640450cec51afc88fb676c0004f797f0a54404ca24e4acc8a2183cd836719dab0e117532580
@@ -0,0 +1,14 @@
1
+ ---
2
+ name: be-decisive-not-menus
3
+ description: "David wants decisive action — make the clear right move, don't present option-menus or re-ask"
4
+ metadata:
5
+ node_type: memory
6
+ type: feedback
7
+ originSessionId: 8ab20c6a-4c03-4f0a-9acd-e12099699ad8
8
+ ---
9
+
10
+ David wants decisive execution, not deliberation theater. He corrected me repeatedly (and bluntly) this session for presenting choices instead of acting, re-offering things already done, and asking permission for obvious next steps.
11
+
12
+ **Why:** He's the maintainer and knows his codebase; a menu of options he has to adjudicate is friction, not help. "and?" from him means "keep going / do the next obvious thing." "do the right thing" means "use your judgment and execute."
13
+
14
+ **How to apply:** When something's already done, say it's done — don't re-offer it. When there's a clear best move (green reviewed PR → merge it; verified-safe branch cleanup → do it), just make it and report the result. Reserve questions for genuine forks where his answer changes the outcome, and even then lead with a one-word-approvable recommendation instead of a survey. Don't narrate options you won't pursue. Confirm only truly hard-to-reverse or outward-facing actions — and expect him to push you past even those with "just do it."
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: flagship-shared-rubocop-config-state
3
+ description: "Status of issue 79AE436E (shared RuboCop config + eject) — designed 2026-07-20, not yet built"
4
+ metadata:
5
+ node_type: memory
6
+ type: project
7
+ originSessionId: 8ab20c6a-4c03-4f0a-9acd-e12099699ad8
8
+ ---
9
+
10
+ The flagship **79AE436E** ("own shared RuboCop config via `inherit_gem` + eject hatch") is **designed but not implemented** as of 2026-07-20. Design spec committed at `docs/superpowers/specs/2026-07-20-shared-rubocop-config-eject-design.md` on branch `file-issue-own-shared-config` (that branch = the flagship WIP: issue-filing commit + design doc only; the 6 backlog fixes that were briefly on it shipped separately via PR #20 and are now in master).
11
+
12
+ Approved direction: `inherit_gem`; gempilot ships `config/rubocop/{base,minitest,rspec}.yml`; generated `.rubocop.yml` shrinks to near-zero (gem-name excludes become globs `*.gemspec`/`lib/*-*.rb`; drop `TargetRubyVersion` since RuboCop reads it from the gemspec's `required_ruby_version`); new `gempilot eject` inlines config + cuts the link; generated Gemfile pins gempilot.
13
+
14
+ Before/while building: **(1) OPEN decision** — CI-break mitigation policy; **pin** is the recommended default but David has NOT confirmed it. **(2) First step is a SPIKE** — verify RuboCop loads `plugins:` (rubocop-claude etc.) *through* an `inherit_gem` config; if not, the plugins list stays in the local file. See [[gempilot-generated-gem-rubocop-gate]].
15
+
16
+ Other open backlog item: **741FFD05** (reduce deps). The gemspec's 4 runtime deps (`command_kit`, `rake`, `warning`, `zeitwerk`) are all used — leave them; reduction is purely Gemfile dev deps. Recommended: drop `benchmark`/`observer`/`rbs`; keep `irb`/`repl_type_completor` (bin/console), `rdoc` (docs), `debug`. (`warning` is the one narrow runtime dep — only silences the VERSION-reload warnings in `Project#fetch_version`.)
data/.rubocop.yml CHANGED
@@ -279,17 +279,3 @@ RSpec/NamedSubject:
279
279
  # have_file_content matcher accepts a filename string in expect()
280
280
  RSpec/ExpectActual:
281
281
  Enabled: false
282
-
283
- Design/PositionalArguments:
284
- Enabled: false
285
-
286
- Design/KeywordArguments:
287
- Enabled: false
288
-
289
- Design/TotalArguments:
290
- Enabled: false
291
-
292
- Design/AgentNounClassName:
293
- Exclude:
294
- - 'lib/gempilot/generator.rb'
295
- - 'test/gempilot/cli/generator_test.rb'
data/CLAUDE.md CHANGED
@@ -10,7 +10,7 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
10
10
  - `gempilot create` — Scaffold a new gem (templates in `data/templates/gem/`)
11
11
  - `gempilot new` — Generate a class, module, or command in an existing gem (templates in `data/templates/new/`)
12
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)
13
+ - `gempilot bump` — Bump version in `version.rb` (patch default, or minor/major/tiny/dev)
14
14
  - `gempilot release` — Thin proxy to `rake release`
15
15
  - `gempilot console` — Thin proxy to `bin/console`
16
16
 
@@ -21,6 +21,7 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
21
21
  - Generator module (`lib/gempilot/cli/generator.rb`) provides template rendering via ERB
22
22
  - GemContext module (`lib/gempilot/cli/gem_context.rb`) shared by new, destroy, release, console
23
23
  - `GemConstant` value object (`lib/gempilot/gem_constant.rb`) owns constant→namespace/path resolution for `new`/`destroy`; constants are rooted at the gem module by construction
24
+ - `SegmentedVersion` value object (`lib/gempilot/segmented_version.rb`) owns version parsing and bump arithmetic; every bump moves to the smallest version of the requested shape greater than the current version (RubyGems ordering)
24
25
  - CommandKit::Commands::AutoLoad maps filenames in `commands/` to command names
25
26
 
26
27
  ### Testing
data/README.md CHANGED
@@ -82,8 +82,16 @@ Bump the version in `lib/<gem>/version.rb`.
82
82
  gempilot bump # patch (default)
83
83
  gempilot bump minor
84
84
  gempilot bump major
85
+ gempilot bump tiny # fourth integer for tiny follow-ups: 0.2.0 -> 0.2.0.1
86
+ gempilot bump dev # preview of the next patch: 0.2.0 -> 0.2.1.dev1
85
87
  ```
86
88
 
89
+ A `dev` version previews the next patch, so it always sorts ahead of the
90
+ current release (`0.2.0 < 0.2.1.dev1 < 0.2.1`), and a numeric bump finalizes
91
+ the cycle (`gempilot bump` from `0.2.1.dev3` gives `0.2.1`). A repo holding an
92
+ old-style dev version — one created *after* its base version shipped — needs a
93
+ one-time hand edit of `version.rb` past the last published version.
94
+
87
95
  ### `gempilot release`
88
96
 
89
97
  Delegates to `rake release`, which publishes the current version to all remotes
@@ -113,7 +121,7 @@ Generated gems include rake tasks for the full version lifecycle:
113
121
  | Task | Description |
114
122
  |------|-------------|
115
123
  | `rake version:current` | Display the current version |
116
- | `rake version:bump` | Increment the patch version |
124
+ | `rake version:bump` | Bump the version (patch default; major/minor/patch/tiny/dev) |
117
125
  | `rake version:commit` | Commit the version file change |
118
126
  | `rake version:tag` | Create a git tag for the version |
119
127
  | `rake version:untag` | Delete the version git tag |
@@ -18,16 +18,8 @@ RuboCop::RakeTask.new
18
18
  require "gempilot/version_task"
19
19
  Gempilot::VersionTask.new
20
20
 
21
- namespace :zeitwerk do
22
- desc "Verify all files follow Zeitwerk naming conventions"
23
- task :validate do
24
- ruby "-e", <<~RUBY
25
- require '<%= @require_path %>'
26
- <%= @module_name %>::LOADER.eager_load(force: true)
27
- puts 'Zeitwerk: All files loaded successfully.'
28
- RUBY
29
- end
30
- end
21
+ require "gempilot/zeitwerk_task"
22
+ Gempilot::ZeitwerkTask.new
31
23
 
32
24
  <% if @test_framework == :minitest -%>
33
25
  task default: [:test, :rubocop]
@@ -6,8 +6,7 @@ require "zeitwerk"
6
6
  ##
7
7
  # Top-level namespace for the <%= @gem_name %> gem.
8
8
  module <%= @module_name %>
9
- LOADER = Zeitwerk::Loader.for_gem
10
- LOADER.setup
9
+ LOADER = Zeitwerk::Loader.for_gem.tap(&:setup)
11
10
 
12
11
  ##
13
12
  # Base error class for <%= @gem_name %>.
@@ -9,8 +9,7 @@ require "zeitwerk"
9
9
  <% end -%>
10
10
  <%= " " * i %>module <%= part %>
11
11
  <% end -%>
12
- <%= " " * @module_parts.size %>LOADER = Zeitwerk::Loader.for_gem_extension(<%= @base_module %>)
13
- <%= " " * @module_parts.size %>LOADER.setup
12
+ <%= " " * @module_parts.size %>LOADER = Zeitwerk::Loader.for_gem_extension(<%= @base_module %>).tap(&:setup)
14
13
 
15
14
  <%= " " * @module_parts.size %>##
16
15
  <%= " " * @module_parts.size %># Base error class for <%= @gem_name %>.
@@ -1,7 +1,10 @@
1
1
  ENV["MT_NO_PLUGINS"] = "1" unless ENV["RM_INFO"]
2
2
 
3
3
  require "minitest/autorun"
4
- require "minitest/reporters"
5
- Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)]
4
+
5
+ unless ENV["RM_INFO"]
6
+ require "minitest/reporters"
7
+ Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(color: true)]
8
+ end
6
9
 
7
10
  require "<%= @require_path %>"
@@ -0,0 +1,483 @@
1
+ # Forward-Moving Version Bumps Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Make every `version:bump` segment move strictly forward under RubyGems ordering, add a `tiny` fourth-integer segment, and mark dev GitHub releases as prereleases.
6
+
7
+ **Architecture:** A new `Gempilot::SegmentedVersion` value object (Data class) owns parsing of the three accepted version shapes (`M.m.p`, `M.m.p.N`, `M.m.p.devN`) and all bump arithmetic; `Project::Version#bump` becomes a thin delegation, keeping its `Data.define(:path, :value)` surface untouched. `GithubRelease#create` derives prerelease status from its tag string. Spec: `docs/superpowers/specs/2026-08-09-dev-version-bump-design.md`.
8
+
9
+ **Tech Stack:** Ruby (Zeitwerk autoloading), RSpec (`spec/`, library), Minitest (`test/`, CLI), RuboCop.
10
+
11
+ ## Global Constraints
12
+
13
+ - Semantics rule (from spec): every bump yields the smallest version of the requested shape strictly greater than the current version. Full transition table is in the spec and is reproduced in Task 1's test code — treat that table as normative.
14
+ - Double-quoted strings (`Style/StringLiterals: double_quotes`); NO `# frozen_string_literal:` comments (`EnforcedStyle: never`); trailing commas in multiline literals/arguments.
15
+ - Ruby ≥ 3.4 idioms are fine (`it` block parameter is used throughout the codebase); rdoc `##` doc blocks (never YARD tags), no blank line between `##` block and the definition.
16
+ - Zeitwerk: new file `lib/gempilot/segmented_version.rb` MUST define `Gempilot::SegmentedVersion` (the eager-load spec `spec/zeitwerk_spec.rb` enforces this).
17
+ - Verification commands: `bundle exec rspec <file>` for focused runs, `bundle exec rake default` (= `test` + `spec` + `rubocop`) for the full gate. Baseline before Task 1: 145 examples, 0 failures, 60 files no rubocop offenses.
18
+ - Commit messages: plain imperative, no conventional-commit prefixes (repo style: "Add issue", "Add dev version bump design spec").
19
+
20
+ ---
21
+
22
+ ### Task 1: SegmentedVersion arithmetic + Version#bump rewrite
23
+
24
+ **Files:**
25
+ - Create: `lib/gempilot/segmented_version.rb`
26
+ - Modify: `lib/gempilot/project/version.rb` (full rewrite shown below)
27
+ - Test: `spec/gempilot/project/version_spec.rb` (full rewrite shown below)
28
+ - Test: `spec/gempilot/version_task_spec.rb:35-38` (one expectation flip)
29
+
30
+ **Interfaces:**
31
+ - Consumes: `Gempilot::Project::Version` `Data.define(:path, :value)` (existing), `Data#with` (Ruby core).
32
+ - Produces: `Gempilot::SegmentedVersion.parse(string) → SegmentedVersion` (raises `ArgumentError` on unrecognized shapes), `SegmentedVersion#bump(segment) → SegmentedVersion` for `:major/:minor/:patch/:tiny/:dev` (symbol or string; raises `ArgumentError` otherwise), `SegmentedVersion#to_s → String`. `Version#bump(segment = :patch)` / `#next_version` keep their existing signatures — later tasks rely only on those.
33
+
34
+ - [ ] **Step 1: Rewrite the version spec with the normative transition table**
35
+
36
+ Replace the entire contents of `spec/gempilot/project/version_spec.rb` with:
37
+
38
+ ```ruby
39
+ require "spec_helper"
40
+
41
+ RSpec.describe Gempilot::Project::Version do
42
+ let(:path) { Pathname("lib/my_gem/version.rb") }
43
+
44
+ def version(value)
45
+ described_class.new(path: path, value: value)
46
+ end
47
+
48
+ describe "#tag" do
49
+ it "prepends v to the value" do
50
+ expect(version("1.2.3").tag).to eq("v1.2.3")
51
+ end
52
+ end
53
+
54
+ describe "#bump" do
55
+ transitions = {
56
+ "1.2.3" => { major: "2.0.0", minor: "1.3.0", patch: "1.2.4", tiny: "1.2.3.1", dev: "1.2.4.dev1" },
57
+ "1.2.3.1" => { major: "2.0.0", minor: "1.3.0", patch: "1.2.4", tiny: "1.2.3.2", dev: "1.2.4.dev1" },
58
+ "1.2.4.dev2" => { major: "2.0.0", minor: "1.3.0", patch: "1.2.4", tiny: "1.2.4.1", dev: "1.2.4.dev3" },
59
+ "1.3.0.dev2" => { major: "2.0.0", minor: "1.3.0", patch: "1.3.0", tiny: "1.3.0.1", dev: "1.3.0.dev3" },
60
+ "2.0.0.dev2" => { major: "2.0.0", minor: "2.0.0", patch: "2.0.0", tiny: "2.0.0.1", dev: "2.0.0.dev3" },
61
+ }
62
+
63
+ transitions.each do |from, bumps|
64
+ bumps.each do |segment, to|
65
+ it "bumps #{from} to #{to} for #{segment}" do
66
+ expect(version(from).bump(segment).value).to eq(to)
67
+ end
68
+ end
69
+ end
70
+
71
+ it "moves every transition strictly forward under Gem::Version ordering" do
72
+ transitions.each do |from, bumps|
73
+ bumps.each_value do |to|
74
+ expect(Gem::Version.new(to)).to be > Gem::Version.new(from)
75
+ end
76
+ end
77
+ end
78
+
79
+ it "bumps patch by default" do
80
+ expect(version("1.2.3").bump.value).to eq("1.2.4")
81
+ end
82
+
83
+ it "accepts segment as a string" do
84
+ expect(version("1.0.0").bump("minor").value).to eq("1.1.0")
85
+ end
86
+
87
+ it "raises for an unknown segment" do
88
+ expect { version("1.0.0").bump(:hotfix) }.to raise_error(ArgumentError, /unknown segment/i)
89
+ end
90
+
91
+ it "raises for a two-integer version" do
92
+ expect { version("1.2").bump }.to raise_error(ArgumentError, /cannot parse/i)
93
+ end
94
+
95
+ it "raises for a non-dev prerelease version" do
96
+ expect { version("1.2.3.beta1").bump }.to raise_error(ArgumentError, /cannot parse/i)
97
+ end
98
+ end
99
+
100
+ describe "#next_version" do
101
+ it "finalizes a dev version to its target" do
102
+ expect(version("0.0.4.dev3").next_version.value).to eq("0.0.4")
103
+ end
104
+
105
+ it "bumps the patch of a release version" do
106
+ expect(version("1.0.99").next_version.value).to eq("1.0.100")
107
+ end
108
+ end
109
+ end
110
+ ```
111
+
112
+ - [ ] **Step 2: Flip the stale expectation in the version task spec**
113
+
114
+ In `spec/gempilot/version_task_spec.rb`, the fixture version is `1.0.0.dev3`. Change the `version:bump` default example (currently expects `"1.0.1"`) to:
115
+
116
+ ```ruby
117
+ it "finalizes a dev version to its target by default" do
118
+ Rake::Task["version:bump"].invoke
119
+ expect(version_in_file).to eq("1.0.0")
120
+ end
121
+ ```
122
+
123
+ (The `version:bump[dev]` → `"1.0.0.dev4"` and both `version:release` examples are already correct under the new semantics — do not touch them.)
124
+
125
+ - [ ] **Step 3: Run the specs to verify they fail for the right reason**
126
+
127
+ Run: `bundle exec rspec spec/gempilot/project/version_spec.rb spec/gempilot/version_task_spec.rb --no-color`
128
+ Expected: FAILURES including `expected: "1.2.4.dev1" got: "1.2.3.dev1"` (dev goes backward today), `expected: "1.2.4" got: "1.2.5"` (patch overshoots a dev target), every `:tiny` cell erroring with `ArgumentError: Unknown segment :tiny` (old code rejects the segment), the `"1.2"` example failing with `NoMethodError` instead of `ArgumentError` (old code coerces then crashes on the missing patch integer), the `"1.2.3.beta1"` example failing with "expected ArgumentError but nothing was raised" (old code silently coerces to `1.2.4`), and `expected: "1.0.0" got: "1.0.1"` in the task spec. The `#tag`, string-segment, and unknown-segment examples still pass.
129
+
130
+ - [ ] **Step 4: Create the SegmentedVersion value object**
131
+
132
+ Create `lib/gempilot/segmented_version.rb`:
133
+
134
+ ```ruby
135
+ module Gempilot
136
+ ## Arithmetic over the three version shapes gempilot accepts: release
137
+ ## (+1.2.3+), tiny release (+1.2.3.1+), and dev prerelease (+1.2.3.dev1+).
138
+ ##
139
+ ## Every bump returns the smallest version of the requested shape that is
140
+ ## strictly greater than the current version under RubyGems ordering, so
141
+ ## bumping always moves a project forward: a dev bump previews the next
142
+ ## patch (+1.2.3+ to +1.2.4.dev1+) and numeric bumps finalize a dev cycle
143
+ ## (+1.2.4.dev2+ to +1.2.4+ for +:patch+).
144
+ SegmentedVersion = Data.define(:major, :minor, :patch, :tiny, :dev) do
145
+ def self.parse(string)
146
+ format = /\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:\.(?<tiny>\d+)|\.dev(?<dev>\d+))?\z/
147
+ match = format.match(string)
148
+ raise ArgumentError, "cannot parse version: #{string.inspect}" unless match
149
+
150
+ new(**match.named_captures.to_h { |name, digits| [name.to_sym, digits&.to_i] })
151
+ end
152
+
153
+ def bump(segment)
154
+ case segment.to_sym
155
+ when :major then bump_major
156
+ when :minor then bump_minor
157
+ when :patch then bump_patch
158
+ when :tiny then bump_tiny
159
+ when :dev then bump_dev
160
+ else raise ArgumentError, "Unknown segment #{segment.inspect}. Use :major, :minor, :patch, :tiny, or :dev"
161
+ end
162
+ end
163
+
164
+ def to_s
165
+ [major, minor, patch, tiny, ("dev#{dev}" if dev)].compact.join(".")
166
+ end
167
+
168
+ private
169
+
170
+ def bump_major
171
+ return finalize if dev && minor.zero? && patch.zero?
172
+
173
+ with(major: major + 1, minor: 0, patch: 0, tiny: nil, dev: nil)
174
+ end
175
+
176
+ def bump_minor
177
+ return finalize if dev && patch.zero?
178
+
179
+ with(minor: minor + 1, patch: 0, tiny: nil, dev: nil)
180
+ end
181
+
182
+ def bump_patch
183
+ dev ? finalize : with(patch: patch + 1, tiny: nil)
184
+ end
185
+
186
+ def bump_tiny
187
+ with(tiny: (tiny || 0) + 1, dev: nil)
188
+ end
189
+
190
+ def bump_dev
191
+ dev ? with(dev: dev + 1) : with(patch: patch + 1, tiny: nil, dev: 1)
192
+ end
193
+
194
+ def finalize
195
+ with(tiny: nil, dev: nil)
196
+ end
197
+ end.freeze
198
+ end
199
+ ```
200
+
201
+ Notes for the implementer:
202
+ - The regex lives in a method-local (`format`), not a constant — a constant inside the `Data.define` block would trip `Lint/ConstantDefinitionInBlock`.
203
+ - `bump` dispatches to five one-branch private methods to stay under `Metrics/CyclomaticComplexity` (max 7).
204
+ - `finalize` drops the prerelease/tiny suffix — it encodes "the smallest version of a numeric shape greater than a dev version is the dev version's own target".
205
+
206
+ - [ ] **Step 5: Rewrite Version#bump as a delegation**
207
+
208
+ Replace the entire contents of `lib/gempilot/project/version.rb` with:
209
+
210
+ ```ruby
211
+ module Gempilot
212
+ class Project
213
+ ## The project's version file (+path+) and value. Bump arithmetic
214
+ ## delegates to SegmentedVersion, so every bump moves strictly forward
215
+ ## under RubyGems ordering.
216
+ Version = Data.define(:path, :value) do
217
+ def tag
218
+ "v#{value}"
219
+ end
220
+
221
+ def bump(segment = :patch)
222
+ with(value: SegmentedVersion.parse(value).bump(segment).to_s)
223
+ end
224
+
225
+ alias_method :next_version, :bump
226
+ end.freeze
227
+ end
228
+ end
229
+ ```
230
+
231
+ - [ ] **Step 6: Run the specs to verify they pass**
232
+
233
+ Run: `bundle exec rspec spec/gempilot/project/version_spec.rb spec/gempilot/version_task_spec.rb --no-color`
234
+ Expected: PASS, 0 failures (34 + task-spec examples). If `zeitwerk` cannot resolve the new constant, the filename does not match `Gempilot::SegmentedVersion` — fix the filename, not the loader.
235
+
236
+ - [ ] **Step 7: Run the full gate**
237
+
238
+ Run: `bundle exec rake default 2>&1 | tail -8`
239
+ Expected: minitest green, all RSpec examples green (count grows from 145), `no offenses detected`.
240
+
241
+ - [ ] **Step 8: Commit**
242
+
243
+ ```bash
244
+ git add lib/gempilot/segmented_version.rb lib/gempilot/project/version.rb spec/gempilot/project/version_spec.rb spec/gempilot/version_task_spec.rb
245
+ git commit -m "Make version bumps move strictly forward"
246
+ ```
247
+
248
+ ---
249
+
250
+ ### Task 2: `tiny` segment on the rake and CLI surfaces
251
+
252
+ **Files:**
253
+ - Modify: `lib/gempilot/version_task.rb:53` (desc string only)
254
+ - Modify: `lib/gempilot/cli/commands/bump.rb`
255
+ - Test: `spec/gempilot/version_task_spec.rb` (add one example)
256
+ - Test: `test/gempilot/cli/bump_command_test.rb` (add one test)
257
+
258
+ **Interfaces:**
259
+ - Consumes: `Version#bump` accepting `:tiny` (Task 1); rake `version:bump[segment]` forwards the segment string verbatim (existing behavior — no logic change needed there).
260
+ - Produces: `gempilot bump tiny` and `rake version:bump[tiny]` as documented user commands; CLI allowed-segment list `%w[patch minor major tiny dev]`.
261
+
262
+ - [ ] **Step 1: Add the failing rake-task example**
263
+
264
+ In `spec/gempilot/version_task_spec.rb`, inside `describe "version:bump"`, add:
265
+
266
+ ```ruby
267
+ it "bumps the tiny segment" do
268
+ Rake::Task["version:bump"].invoke("tiny")
269
+ expect(version_in_file).to eq("1.0.0.1")
270
+ end
271
+ ```
272
+
273
+ (Fixture is `1.0.0.dev3`; `tiny` finalizes the core and appends `.1`.)
274
+
275
+ - [ ] **Step 2: Add the failing CLI test**
276
+
277
+ In `test/gempilot/cli/bump_command_test.rb`, after `test_bump_dev_invokes_rake`, add:
278
+
279
+ ```ruby
280
+ def test_bump_tiny_invokes_rake
281
+ calls = recorded_system_calls { |cmd| cmd.main(["tiny"]) }
282
+
283
+ assert_includes calls, ["bundle", "exec", "rake", "version:bump[tiny]"]
284
+ end
285
+ ```
286
+
287
+ - [ ] **Step 3: Run both to verify the CLI test fails and the rake example passes**
288
+
289
+ Run: `bundle exec rspec spec/gempilot/version_task_spec.rb --no-color && bundle exec rake test 2>&1 | tail -4`
290
+ Expected: the rake-task example PASSES already (the task forwards any segment and Task 1 taught `Version#bump` about `:tiny`) — it is a pin, not a driver. The minitest CLI test FAILS: `Bump#validate_segment` rejects `tiny` and exits 1 before calling rake.
291
+
292
+ - [ ] **Step 4: Teach the CLI command the tiny segment**
293
+
294
+ In `lib/gempilot/cli/commands/bump.rb` apply these four edits:
295
+
296
+ ```ruby
297
+ description "Bump the gem version (patch by default, or minor/major/tiny/dev)"
298
+
299
+ examples [
300
+ "",
301
+ "patch",
302
+ "minor",
303
+ "major",
304
+ "tiny",
305
+ "dev",
306
+ ]
307
+
308
+ argument :segment, required: false,
309
+ desc: "Version segment to bump: patch (default), minor, major, tiny, or dev"
310
+ ```
311
+
312
+ and in `validate_segment`:
313
+
314
+ ```ruby
315
+ return segment if %w[patch minor major tiny dev].include?(segment)
316
+
317
+ puts colors.red("Unknown segment '#{segment}'. Use patch, minor, major, tiny, or dev.")
318
+ ```
319
+
320
+ - [ ] **Step 5: Update the rake task description**
321
+
322
+ In `lib/gempilot/version_task.rb`, `define_bump_task`:
323
+
324
+ ```ruby
325
+ desc "Bump version (patch default; segments: major, minor, patch, tiny, dev)"
326
+ ```
327
+
328
+ - [ ] **Step 6: Run the full gate**
329
+
330
+ Run: `bundle exec rake default 2>&1 | tail -8`
331
+ Expected: all green, no offenses. (`test_bump_fails_with_invalid_segment` uses `hotfix`, which stays invalid.)
332
+
333
+ - [ ] **Step 7: Commit**
334
+
335
+ ```bash
336
+ git add lib/gempilot/version_task.rb lib/gempilot/cli/commands/bump.rb spec/gempilot/version_task_spec.rb test/gempilot/cli/bump_command_test.rb
337
+ git commit -m "Add tiny segment to bump interfaces"
338
+ ```
339
+
340
+ ---
341
+
342
+ ### Task 3: Mark dev GitHub releases as prereleases
343
+
344
+ **Files:**
345
+ - Modify: `lib/gempilot/github_release.rb`
346
+ - Test: `spec/gempilot/github_release_spec.rb`
347
+
348
+ **Interfaces:**
349
+ - Consumes: `GithubRelease.new(tag)` with tag strings like `"v1.2.4.dev1"` (signature unchanged); `Gem::Version#prerelease?` (rubygems, always loaded).
350
+ - Produces: `gh release create` invoked with `--prerelease` when and only when the tag names a prerelease version.
351
+
352
+ - [ ] **Step 1: Add the failing specs**
353
+
354
+ In `spec/gempilot/github_release_spec.rb`, inside `describe "#create"`, add two contexts after the existing examples:
355
+
356
+ ```ruby
357
+ context "with a dev prerelease tag" do
358
+ let(:tag) { "v1.2.4.dev1" }
359
+
360
+ it "marks the release as a prerelease" do
361
+ release.create
362
+ args = ["gh", "release", "create", "--generate-notes", "--fail-on-no-commits", "--prerelease", tag]
363
+ expect(release).to have_received(:sh).with(*args)
364
+ end
365
+ end
366
+
367
+ context "with a tiny release tag" do
368
+ let(:tag) { "v1.2.3.1" }
369
+
370
+ it "does not mark the release as a prerelease" do
371
+ release.create
372
+ args = ["gh", "release", "create", "--generate-notes", "--fail-on-no-commits", tag]
373
+ expect(release).to have_received(:sh).with(*args)
374
+ end
375
+ end
376
+ ```
377
+
378
+ - [ ] **Step 2: Run to verify the dev context fails**
379
+
380
+ Run: `bundle exec rspec spec/gempilot/github_release_spec.rb --no-color`
381
+ Expected: FAIL — the dev-tag example receives no `--prerelease` argument; the tiny-tag example passes.
382
+
383
+ - [ ] **Step 3: Implement the prerelease flag**
384
+
385
+ In `lib/gempilot/github_release.rb`, replace `#create` and append a private section after `#list`:
386
+
387
+ ```ruby
388
+ def create
389
+ sh "gh", "release", "create",
390
+ "--generate-notes", "--fail-on-no-commits",
391
+ *prerelease_flag,
392
+ tag
393
+ end
394
+ ```
395
+
396
+ ```ruby
397
+ private
398
+
399
+ def prerelease_flag
400
+ Gem::Version.new(tag.delete_prefix("v")).prerelease? ? ["--prerelease"] : []
401
+ end
402
+ ```
403
+
404
+ Also extend the class doc block's first line to:
405
+
406
+ ```ruby
407
+ ## Manages GitHub releases for a version tag. Tags naming a prerelease
408
+ ## version (e.g. +v1.2.4.dev1+) are created as GitHub prereleases.
409
+ ```
410
+
411
+ - [ ] **Step 4: Run the spec, then the full gate**
412
+
413
+ Run: `bundle exec rspec spec/gempilot/github_release_spec.rb --no-color && bundle exec rake default 2>&1 | tail -8`
414
+ Expected: PASS everywhere, no offenses.
415
+
416
+ - [ ] **Step 5: Commit**
417
+
418
+ ```bash
419
+ git add lib/gempilot/github_release.rb spec/gempilot/github_release_spec.rb
420
+ git commit -m "Mark dev GitHub releases as prereleases"
421
+ ```
422
+
423
+ ---
424
+
425
+ ### Task 4: Documentation
426
+
427
+ **Files:**
428
+ - Modify: `README.md` (bump section ~line 77-85; version tasks table row ~line 116)
429
+ - Modify: `CLAUDE.md` (bump command bullet; architecture list)
430
+
431
+ **Interfaces:**
432
+ - Consumes: final semantics from Tasks 1-3.
433
+ - Produces: user-facing docs; no code.
434
+
435
+ - [ ] **Step 1: Update the README bump section**
436
+
437
+ Replace the `gempilot bump` code block and add one paragraph after it:
438
+
439
+ ````markdown
440
+ ```bash
441
+ gempilot bump # patch (default)
442
+ gempilot bump minor
443
+ gempilot bump major
444
+ gempilot bump tiny # fourth integer for tiny follow-ups: 0.2.0 -> 0.2.0.1
445
+ gempilot bump dev # preview of the next patch: 0.2.0 -> 0.2.1.dev1
446
+ ```
447
+
448
+ A `dev` version previews the next patch, so it always sorts ahead of the
449
+ current release (`0.2.0 < 0.2.1.dev1 < 0.2.1`), and a numeric bump finalizes
450
+ the cycle (`gempilot bump` from `0.2.1.dev3` gives `0.2.1`). A repo holding an
451
+ old-style dev version — one created *after* its base version shipped — needs a
452
+ one-time hand edit of `version.rb` past the last published version.
453
+ ````
454
+
455
+ - [ ] **Step 2: Update the README version tasks table row**
456
+
457
+ ```markdown
458
+ | `rake version:bump` | Bump the version (patch default; major/minor/patch/tiny/dev) |
459
+ ```
460
+
461
+ - [ ] **Step 3: Update CLAUDE.md**
462
+
463
+ Change the bump command bullet to:
464
+
465
+ ```markdown
466
+ - `gempilot bump` — Bump version in `version.rb` (patch default, or minor/major/tiny/dev)
467
+ ```
468
+
469
+ and add to the Architecture list, after the `GemConstant` bullet:
470
+
471
+ ```markdown
472
+ - `SegmentedVersion` value object (`lib/gempilot/segmented_version.rb`) owns version parsing and bump arithmetic; every bump moves to the smallest version of the requested shape greater than the current version (RubyGems ordering)
473
+ ```
474
+
475
+ - [ ] **Step 4: Full gate and commit**
476
+
477
+ Run: `bundle exec rake default 2>&1 | tail -8`
478
+ Expected: all green (docs only — this is a regression guard).
479
+
480
+ ```bash
481
+ git add README.md CLAUDE.md
482
+ git commit -m "Update docs for forward-moving version bumps"
483
+ ```