gempilot 0.3.0 → 0.3.2
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 +4 -4
- data/.claude/memory/be-decisive-not-menus.md +14 -0
- data/.claude/memory/flagship-shared-rubocop-config-state.md +16 -0
- data/.ruby-version +1 -1
- data/CLAUDE.md +2 -1
- data/README.md +9 -1
- data/data/templates/gem/Gemfile.erb +1 -0
- data/docs/superpowers/plans/2026-08-09-dev-version-bump.md +483 -0
- data/docs/superpowers/plans/2026-09-23-command-generation-bootstrap.md +810 -0
- data/docs/superpowers/plans/2026-09-23-land-betterleaks-jruby.md +262 -0
- data/docs/superpowers/plans/2026-09-23-multi-gem-rubygems-push.md +318 -0
- data/docs/superpowers/plans/2026-09-23-zeitwerk-task-inflector.md +911 -0
- data/docs/superpowers/specs/2026-08-09-dev-version-bump-design.md +163 -0
- data/issues.rec +107 -2
- data/lib/gempilot/cli/command.rb +1 -1
- data/lib/gempilot/cli/commands/bump.rb +5 -4
- data/lib/gempilot/github_release.rb +9 -1
- data/lib/gempilot/project/version.rb +4 -25
- data/lib/gempilot/segmented_version.rb +64 -0
- data/lib/gempilot/version.rb +1 -1
- data/lib/gempilot/version_task.rb +1 -1
- data/vendor/vendored.gemv +0 -0
- metadata +11 -2
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Dev version bump semantics — design
|
|
2
|
+
|
|
3
|
+
**Issue:** `2360FFA4-9388-11F1-8474-FE6CB9572C2F` — "rake version:bump[dev] goes backward rather than forward"
|
|
4
|
+
|
|
5
|
+
**Date:** 2026-08-09
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
`Gempilot::Project::Version#bump(:dev)` appends `.dev1` to the *current* version
|
|
10
|
+
(`0.2.0` → `0.2.0.dev1`). RubyGems treats any version with a letter segment as a
|
|
11
|
+
**prerelease of the version it names** (`Gem::Version` rdoc: "Prereleases sort
|
|
12
|
+
between real releases (newest to oldest): 1.0 > 1.0.b1 > 1.0.a.2 > 0.9"), so
|
|
13
|
+
`0.2.0.dev1 < 0.2.0` — the bump moves the project *backward*. Two related gaps:
|
|
14
|
+
|
|
15
|
+
1. `bump(:patch)` from `X.Y.Z.devN` produces `X.Y.(Z+1)`, overshooting the
|
|
16
|
+
`X.Y.Z` the dev cycle was working toward.
|
|
17
|
+
2. There is no way to bump a fourth integer (`0.2.0` → `0.2.0.1`), the
|
|
18
|
+
rubygems-legal *non-prerelease* shape for tiny follow-up fixes on a released
|
|
19
|
+
version (the Rails security-release pattern, `6.0.3.1`).
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
|
|
23
|
+
- **Dev versions preview the next patch.** `bump(:dev)` from a release version
|
|
24
|
+
yields the next patch's first dev prerelease (`0.2.0` → `0.2.1.dev1`); from a
|
|
25
|
+
dev version it increments the counter (`0.2.1.dev1` → `0.2.1.dev2`). This is
|
|
26
|
+
the only reading consistent with rubygems ordering, and matches ecosystem
|
|
27
|
+
practice (Rails `7.1.0.beta1` precedes `7.1.0`; Bundler's in-repo `.dev`).
|
|
28
|
+
- **One rule governs every bump: move to the smallest version of the requested
|
|
29
|
+
shape that is strictly greater than the current version.** Consequences:
|
|
30
|
+
numeric bumps from a dev version *finalize* the cycle (`0.2.1.dev3` +
|
|
31
|
+
`patch` → `0.2.1`, not `0.2.2`), and `minor`/`major` redirect it
|
|
32
|
+
(`0.2.1.dev3` + `minor` → `0.3.0`).
|
|
33
|
+
- **New `tiny` segment for the fourth integer.** `bump(:tiny)`: `0.2.0` →
|
|
34
|
+
`0.2.0.1` → `0.2.0.2`. Name chosen by elimination: semver's "build metadata"
|
|
35
|
+
is precedence-ignored (and the `Gem::Version` rdoc uses "build" for the third
|
|
36
|
+
integer); `micro` is Python's third digit; `patchlevel` is Ruby-historical but
|
|
37
|
+
long and confusable with `patch`. `tiny` reads as "smaller than patch", which
|
|
38
|
+
is where it sorts. (Old Rails used TINY for the third digit; accepted.)
|
|
39
|
+
- **No dev target argument** (`bump[dev,minor]` → `0.3.0.dev1`) — YAGNI. The
|
|
40
|
+
shape rule already finalizes hand-edited `M.m.0.devN` / `M.0.0.devN` states
|
|
41
|
+
correctly, so a targeted form can be layered on later without breaking
|
|
42
|
+
anything.
|
|
43
|
+
- **Dev GitHub releases are prereleases.** `release:github` passes
|
|
44
|
+
`--prerelease` to `gh release create` when the tag names a prerelease
|
|
45
|
+
version, so `v0.2.1.dev2` does not become the repo's "Latest" release.
|
|
46
|
+
- **Strict version parsing.** `Version#bump` recognizes exactly three forms —
|
|
47
|
+
`M.m.p`, `M.m.p.N`, `M.m.p.devN` — and raises `ArgumentError` for anything
|
|
48
|
+
else, instead of today's silent first-three-integers coercion.
|
|
49
|
+
|
|
50
|
+
## Semantics
|
|
51
|
+
|
|
52
|
+
Accepted version forms: release `M.m.p`, tiny release `M.m.p.N`, dev prerelease
|
|
53
|
+
`M.m.p.devN` (parse: `/\A(\d+)\.(\d+)\.(\d+)(?:\.(\d+)|\.dev(\d+))?\z/`).
|
|
54
|
+
|
|
55
|
+
Full transition table (rows marked * arise only by hand-editing; the rule is
|
|
56
|
+
total over all three forms regardless of how the version got there):
|
|
57
|
+
|
|
58
|
+
| from ↓ bump → | `major` | `minor` | `patch` | `tiny` | `dev` |
|
|
59
|
+
| ------------- | ------- | ------- | ------- | --------- | ------------ |
|
|
60
|
+
| `1.2.3` | `2.0.0` | `1.3.0` | `1.2.4` | `1.2.3.1` | `1.2.4.dev1` |
|
|
61
|
+
| `1.2.3.1` | `2.0.0` | `1.3.0` | `1.2.4` | `1.2.3.2` | `1.2.4.dev1` |
|
|
62
|
+
| `1.2.4.dev2` | `2.0.0` | `1.3.0` | `1.2.4` | `1.2.4.1` | `1.2.4.dev3` |
|
|
63
|
+
| `1.3.0.dev2`* | `2.0.0` | `1.3.0` | `1.3.0` | `1.3.0.1` | `1.3.0.dev3` |
|
|
64
|
+
| `2.0.0.dev2`* | `2.0.0` | `2.0.0` | `2.0.0` | `2.0.0.1` | `2.0.0.dev3` |
|
|
65
|
+
|
|
66
|
+
All 25 transitions verified strictly increasing under `Gem::Version` (checked
|
|
67
|
+
2026-08-09 against ruby's installed rubygems).
|
|
68
|
+
|
|
69
|
+
Branch logic per segment, where the numeric core is `M.m.p` and `dev?` means
|
|
70
|
+
the current version is a dev prerelease:
|
|
71
|
+
|
|
72
|
+
- `major`: `dev? && m == 0 && p == 0` → `M.0.0` (finalize); else `(M+1).0.0`
|
|
73
|
+
- `minor`: `dev? && p == 0` → `M.m.0` (finalize); else `M.(m+1).0`
|
|
74
|
+
- `patch`: `dev?` → `M.m.p` (finalize); else `M.m.(p+1)`
|
|
75
|
+
- `tiny`: `M.m.p.(N+1)` where `N` is the current fourth integer, else 0.
|
|
76
|
+
(From a dev version this yields `M.m.p.1` — it implicitly finalizes the
|
|
77
|
+
target and adds a tiny level; forward-moving, documented edge case.)
|
|
78
|
+
- `dev`: `dev?` → `M.m.p.dev(N+1)`; else `M.m.(p+1).dev1`
|
|
79
|
+
|
|
80
|
+
## Components
|
|
81
|
+
|
|
82
|
+
### `Gempilot::Project::Version` (modified — `lib/gempilot/project/version.rb`)
|
|
83
|
+
|
|
84
|
+
- Stays `Data.define(:path, :value)` — `write_version!`, `VersionTag`, and all
|
|
85
|
+
call sites are untouched. Parsing is internal to `bump`.
|
|
86
|
+
- `bump(segment)` accepts `:major, :minor, :patch, :tiny, :dev` (symbol or
|
|
87
|
+
string), implements the table above. Unknown segment → `ArgumentError`
|
|
88
|
+
naming the valid segments (as today, plus `tiny`). Unparseable value →
|
|
89
|
+
`ArgumentError` naming the value.
|
|
90
|
+
- `next_version` alias retained; its behavior follows the new `patch` default
|
|
91
|
+
(`0.0.4.dev3` → `0.0.4`, previously `0.0.5`).
|
|
92
|
+
|
|
93
|
+
### `Gempilot::GithubRelease` (modified — `lib/gempilot/github_release.rb`)
|
|
94
|
+
|
|
95
|
+
- `#create` appends `--prerelease` when the tag (sans leading `v`) parses as a
|
|
96
|
+
prerelease per `Gem::Version#prerelease?`. Self-contained — the
|
|
97
|
+
`initialize(tag)` signature and all call sites are unchanged. `#destroy` /
|
|
98
|
+
`#list` untouched.
|
|
99
|
+
|
|
100
|
+
### `Gempilot::VersionTask` (modified — `lib/gempilot/version_task.rb`)
|
|
101
|
+
|
|
102
|
+
- Behavior unchanged (it forwards the segment). Only the `version:bump` desc
|
|
103
|
+
string updates: "Bump version (patch default; segments: major, minor, patch,
|
|
104
|
+
tiny, dev)".
|
|
105
|
+
|
|
106
|
+
### `Gempilot::CLI::Commands::Bump` (modified — `lib/gempilot/cli/commands/bump.rb`)
|
|
107
|
+
|
|
108
|
+
- Allowed segments become `%w[patch minor major tiny dev]`; error message,
|
|
109
|
+
argument desc, command description, and `examples` updated to match.
|
|
110
|
+
|
|
111
|
+
## Testing (TDD — written before implementation)
|
|
112
|
+
|
|
113
|
+
**`spec/gempilot/project/version_spec.rb` (rewritten)**
|
|
114
|
+
|
|
115
|
+
- The full 5×5 transition table above, each cell an example.
|
|
116
|
+
- A monotonicity property spec: for every table cell,
|
|
117
|
+
`Gem::Version.new(new) > Gem::Version.new(old)` — the direct regression spec
|
|
118
|
+
for this issue.
|
|
119
|
+
- Segment as string; unknown segment `ArgumentError`; unparseable value
|
|
120
|
+
(`"1.2"`, `"1.2.3.beta1"`) `ArgumentError`; `next_version` follows patch
|
|
121
|
+
semantics (`0.0.4.dev3` → `0.0.4`).
|
|
122
|
+
|
|
123
|
+
**`spec/gempilot/version_task_spec.rb` (expectations updated)**
|
|
124
|
+
|
|
125
|
+
Fixture starts at `1.0.0.dev3`:
|
|
126
|
+
- `version:bump` (default patch) → `1.0.0` (finalize; was `1.0.1`).
|
|
127
|
+
- `version:bump[dev]` → `1.0.0.dev4` (unchanged).
|
|
128
|
+
- `version:bump[tiny]` → `1.0.0.1` (new example).
|
|
129
|
+
- `version:release[dev]` → commit "Bump version to 1.0.0.dev4", tag
|
|
130
|
+
`v1.0.0.dev4` (unchanged).
|
|
131
|
+
|
|
132
|
+
**`spec/gempilot/github_release_spec.rb` (extended)**
|
|
133
|
+
|
|
134
|
+
- `create` with `v1.2.4.dev1` includes `--prerelease`; with `v1.2.4` and
|
|
135
|
+
`v1.2.3.1` it does not.
|
|
136
|
+
|
|
137
|
+
**`test/gempilot/cli/bump_command_test.rb` (extended)**
|
|
138
|
+
|
|
139
|
+
- `tiny` accepted as a segment; unknown-segment error message lists it.
|
|
140
|
+
|
|
141
|
+
## Docs
|
|
142
|
+
|
|
143
|
+
- `README.md` — `gempilot bump` section gains `tiny` and `dev` examples with a
|
|
144
|
+
one-line explanation of dev semantics; the rake tasks table row for
|
|
145
|
+
`version:bump` mentions the segments.
|
|
146
|
+
- `CLAUDE.md` — version lifecycle bullet: "(patch default, or
|
|
147
|
+
minor/major/tiny/dev)".
|
|
148
|
+
|
|
149
|
+
## Migration
|
|
150
|
+
|
|
151
|
+
A repo sitting on an old-style dev version (e.g. `0.2.0.dev1` created *after*
|
|
152
|
+
`0.2.0` shipped) is already in the broken state this fixes; `bump(:patch)`
|
|
153
|
+
would finalize to the already-released `0.2.0` (and `version:tag` would then
|
|
154
|
+
fail on the existing tag). Recovery is a one-time hand edit of `version.rb`
|
|
155
|
+
past the last published version. Noted in the README dev-example line.
|
|
156
|
+
|
|
157
|
+
## Out of scope
|
|
158
|
+
|
|
159
|
+
- Targeted dev cycles (`bump[dev,minor]`) — compatible later extension.
|
|
160
|
+
- Other prerelease markers (`pre`, `rc`, `beta`) — `dev` sorts before all of
|
|
161
|
+
them, so they remain addable later.
|
|
162
|
+
- RubyGems prerelease yanking; `data/templates/gem/Rakefile.erb` (no change
|
|
163
|
+
needed — it just instantiates `Gempilot::VersionTask`).
|
data/issues.rec
CHANGED
|
@@ -110,8 +110,8 @@ Description: e.g.
|
|
|
110
110
|
+ Zeitwerk::Loader.for_gem.tap do |l|
|
|
111
111
|
+ l.setup
|
|
112
112
|
+ end
|
|
113
|
-
+
|
|
114
|
-
+
|
|
113
|
+
+
|
|
114
|
+
+
|
|
115
115
|
Status: closed
|
|
116
116
|
|
|
117
117
|
Id: 5618BB20-4325-11F1-BF5A-FE6CB9572C2F
|
|
@@ -212,3 +212,108 @@ Description: the github release related tasks are invoked as follows:
|
|
|
212
212
|
+
|
|
213
213
|
+ Also, there is an issue that prevents github releasing from working at all via the old tasks. I get an error every time saying that "tags have already been pushed" or something along those lines. Ensure that is fixed as well
|
|
214
214
|
Status: closed
|
|
215
|
+
|
|
216
|
+
Id: 986E0100-88F4-11F1-B718-FE6CB9572C2D
|
|
217
|
+
Updated: Sun, 26 Jul 2026 09:19:13 -0400
|
|
218
|
+
Title: Make rbs and the other gems GP installs mri only
|
|
219
|
+
Description: This tool is also used with jruby, and it fails irb install due to rbs
|
|
220
|
+
Status: open
|
|
221
|
+
|
|
222
|
+
Id: 3FFE7616-88F5-11F1-8D3B-FE6CB9572C2D
|
|
223
|
+
Updated: Sun, 26 Jul 2026 09:23:54 -0400
|
|
224
|
+
Title: Add betterleaks to gem setup
|
|
225
|
+
Description: Ensure betterleaks is integrated into the commit and build pipelines, via commit hooks, rake tasks, and/or whatever the idiomatic way is to integrate betterleaks into a rubyproject
|
|
226
|
+
Status: open
|
|
227
|
+
|
|
228
|
+
Id: F6C6EB04-8DA4-11F1-91EF-FA41C3164F3A
|
|
229
|
+
Updated: Sat, 01 Aug 2026 12:31:47 +0000
|
|
230
|
+
Title: release:rubygem_push only handles one gem per version
|
|
231
|
+
Description: rake release:rubygems can publish only one gem per version. A project that publishes several -- platform gems each carrying a compiled executable, say -- either publishes one of them and silently leaves the rest, or, once it replaces the push task with a pkg glob, tries to re-push versions already on rubygems and stops halfway through the release.
|
|
232
|
+
+
|
|
233
|
+
+ MECHANISM
|
|
234
|
+
+
|
|
235
|
+
+ gempilot, lib/gempilot/release_tasks.rb: ReleaseTasks#define_rubygems_release wires release:rubygems to [build, release:guard_clean, release:source_control_push, release:rubygem_push], and, as the module's own rdoc says, expects bundler/gem_tasks to have supplied build, release:guard_clean and release:rubygem_push.
|
|
236
|
+
+
|
|
237
|
+
+ bundler 4.0.17, lib/bundler/gem_helper.rb: GemHelper#install_tasks defines
|
|
238
|
+
+
|
|
239
|
+
+ task "release:rubygem_push" => "build" do
|
|
240
|
+
+ rubygem_push(built_gem_path) if gem_push?
|
|
241
|
+
+ end
|
|
242
|
+
+
|
|
243
|
+
+ built_gem_path is the single .gem that #build_gem produced. One build, one gem, one push. Nothing in that chain can carry a second gem, so a project publishing more than one has to replace both build and release:rubygem_push itself.
|
|
244
|
+
+
|
|
245
|
+
+ There is no glob in gempilot's own source to go looking for. The glob is what a downstream project ends up writing once it takes over the push, and it is wrong in a way gempilot's own version:bump triggers.
|
|
246
|
+
+
|
|
247
|
+
+ REPRODUCE
|
|
248
|
+
+
|
|
249
|
+
+ 1. A gem publishing more than one gem per version. aws-session-manager on gempilot 0.3.0 publishes five: x86_64-linux, aarch64-linux, x86_64-darwin and arm64-darwin, each carrying a compiled session-manager-plugin executable, plus the plain ruby gem.
|
|
250
|
+
+ 2. It replaces build, and replaces the push with the obvious implementation:
|
|
251
|
+
+ task(:rubygem_push) { PACKAGES.glob("*.gem").each { sh "gem", "push", it.to_s } }
|
|
252
|
+
+ 3. rake build && rake release:rubygems -- 0.0.1 publishes.
|
|
253
|
+
+ 4. rake version:bump && rake build -- pkg/ now holds both 0.0.1 and 0.0.2 gems. Nothing empties pkg/ between versions; bundler never has, and gempilot's version tasks do not either.
|
|
254
|
+
+ 5. rake release:rubygems -- gem push rejects the 0.0.1 gems ("Repushing of gem versions is not allowed"), sh raises on the non-zero status, and the release aborts with part of 0.0.2 published and the rest not. Re-running repeats the failure, because the stale gems are still there.
|
|
255
|
+
+
|
|
256
|
+
+ FIX APPLIED DOWNSTREAM
|
|
257
|
+
+
|
|
258
|
+
+ aws-session-manager stopped globbing and took the list from the specifications its build packages, so that gem:build and the push share one source of truth:
|
|
259
|
+
+
|
|
260
|
+
+ PACKAGED = [*BUILDS.map { it.package.to_s }, GENERIC_PACKAGE].freeze
|
|
261
|
+
+
|
|
262
|
+
+ namespace :gem do
|
|
263
|
+
+ task build: PACKAGED
|
|
264
|
+
+ end
|
|
265
|
+
+
|
|
266
|
+
+ namespace :release do
|
|
267
|
+
+ task(:rubygem_push) { PACKAGED.each { sh "gem", "push", it } }
|
|
268
|
+
+ end
|
|
269
|
+
+
|
|
270
|
+
+ FIX SUGGESTED HERE
|
|
271
|
+
+
|
|
272
|
+
+ gempilot should own release:rubygem_push rather than inherit bundler's, and push every gem in pkg belonging to the version being released. It cannot know how a project builds, but it knows the name and the version, which is enough:
|
|
273
|
+
+
|
|
274
|
+
+ def define_rubygem_push(project)
|
|
275
|
+
+ clear_task "release:rubygem_push"
|
|
276
|
+
+ task "release:rubygem_push" => "build" do
|
|
277
|
+
+ packages(project).each { sh "gem", "push", it.to_s }
|
|
278
|
+
+ end
|
|
279
|
+
+ end
|
|
280
|
+
+
|
|
281
|
+
+ def packages(project)
|
|
282
|
+
+ stem = "#{project.name}-#{project.version_value}"
|
|
283
|
+
+ project.root.glob("pkg/{#{stem}.gem,#{stem}-*.gem}")
|
|
284
|
+
+ end
|
|
285
|
+
+
|
|
286
|
+
+ Project#name, #version_value and #root already exist in lib/gempilot/project.rb.
|
|
287
|
+
+
|
|
288
|
+
+ Two details worth keeping:
|
|
289
|
+
+
|
|
290
|
+
+ - The brace glob matches gempilot-0.3.0.gem and gempilot-0.3.0-x86_64-linux.gem, and does not match gempilot-0.3.10.gem. A bare "#{stem}*.gem" would match the latter and push the wrong version.
|
|
291
|
+
+ - An empty result should abort. Otherwise a project whose build writes gems elsewhere gets a release that reports success having published nothing, which is worse than the bug being fixed.
|
|
292
|
+
+
|
|
293
|
+
+ Single-gem projects are unaffected: pkg holds one gem for the current version, and it gets pushed, as today. Multi-gem projects stop having to reinvent the push.
|
|
294
|
+
+
|
|
295
|
+
+ If gempilot would rather not own the task, then the ReleaseTasks rdoc should state the contract instead: a project that replaces build must also replace release:rubygem_push, and must scope the push to the version being released rather than globbing pkg.
|
|
296
|
+
Status: open
|
|
297
|
+
|
|
298
|
+
Id: 2360FFA4-9388-11F1-8474-FE6CB9572C2F
|
|
299
|
+
Updated: Wed, 23 Sep 2026 02:07:20 +0000
|
|
300
|
+
Title: rake version:bump[dev] goes backward rather than forward
|
|
301
|
+
Description: rubygems treats 0.2.0.dev as an *older* version than 0.2.0. The way gempilot handles dev version needs to be rethought. Additionally, how does a user trigger a bump on the fourth integer, e.g. 0.2.0.1?
|
|
302
|
+
Status: closed
|
|
303
|
+
|
|
304
|
+
Id: 69816386-9F7F-11F1-8629-FE6CB9572C2F
|
|
305
|
+
Updated: Mon, 24 Aug 2026 01:48:20 -0400
|
|
306
|
+
Title: Command generation is incomplete
|
|
307
|
+
Description: it adds a "command" that is useless. It should:
|
|
308
|
+
+ 1. at the very least, chmod +x the "exe" in exe
|
|
309
|
+
+ 2. install command_kit
|
|
310
|
+
+ 3. bootstrap a command_kit setup
|
|
311
|
+
Status: open
|
|
312
|
+
|
|
313
|
+
Id: 93D7E88E-B6EB-11F1-8AF9-FE6CB9572C2E
|
|
314
|
+
Updated: Tue, 22 Sep 2026 21:10:33 -0400
|
|
315
|
+
Title: Problems with zeitwerk_task.rb
|
|
316
|
+
Description: 1. Documentation is improperly formatted. sizing is all over the place. 2. The task is broken for projects that use an inflector, like ECS. The task looks for "Ecs" instead, despite having it set up in the zeitwerk config.
|
|
317
|
+
+
|
|
318
|
+
+ Fixing this should rely on idiomatic zeitwerk & zeitwerk inflector usage, and thoroughly thinking through how to fit this into user's loader. DO NOT rely on regexes of any kind to handle this.
|
|
319
|
+
Status: open
|
data/lib/gempilot/cli/command.rb
CHANGED
|
@@ -8,18 +8,19 @@ module Gempilot
|
|
|
8
8
|
include GemContext
|
|
9
9
|
|
|
10
10
|
usage "[options] [SEGMENT]"
|
|
11
|
-
description "Bump the gem version (patch by default, or minor/major/dev)"
|
|
11
|
+
description "Bump the gem version (patch by default, or minor/major/tiny/dev)"
|
|
12
12
|
|
|
13
13
|
examples [
|
|
14
14
|
"",
|
|
15
15
|
"patch",
|
|
16
16
|
"minor",
|
|
17
17
|
"major",
|
|
18
|
+
"tiny",
|
|
18
19
|
"dev",
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
argument :segment, required: false,
|
|
22
|
-
desc: "Version segment to bump: patch (default), minor, major, or dev"
|
|
23
|
+
desc: "Version segment to bump: patch (default), minor, major, tiny, or dev"
|
|
23
24
|
|
|
24
25
|
def run(segment = "patch")
|
|
25
26
|
detect_gem_context
|
|
@@ -31,9 +32,9 @@ module Gempilot
|
|
|
31
32
|
|
|
32
33
|
def validate_segment(segment)
|
|
33
34
|
segment = segment.downcase
|
|
34
|
-
return segment if %w[patch minor major dev].include?(segment)
|
|
35
|
+
return segment if %w[patch minor major tiny dev].include?(segment)
|
|
35
36
|
|
|
36
|
-
puts colors.red("Unknown segment '#{segment}'. Use patch, minor, major, or dev.")
|
|
37
|
+
puts colors.red("Unknown segment '#{segment}'. Use patch, minor, major, tiny, or dev.")
|
|
37
38
|
exit 1
|
|
38
39
|
end
|
|
39
40
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module Gempilot
|
|
2
|
-
## Manages GitHub releases for a version tag.
|
|
2
|
+
## Manages GitHub releases for a version tag. Tags naming a prerelease
|
|
3
|
+
## version (e.g. +v1.2.4.dev1+) are created as GitHub prereleases.
|
|
3
4
|
class GithubRelease
|
|
4
5
|
include StrictShell
|
|
5
6
|
|
|
@@ -12,6 +13,7 @@ module Gempilot
|
|
|
12
13
|
def create
|
|
13
14
|
sh "gh", "release", "create",
|
|
14
15
|
"--generate-notes", "--fail-on-no-commits",
|
|
16
|
+
*prerelease_flag,
|
|
15
17
|
tag
|
|
16
18
|
end
|
|
17
19
|
|
|
@@ -24,5 +26,11 @@ module Gempilot
|
|
|
24
26
|
def list
|
|
25
27
|
sh "gh", "release", "list"
|
|
26
28
|
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def prerelease_flag
|
|
33
|
+
Gem::Version.new(tag.delete_prefix("v")).prerelease? ? ["--prerelease"] : []
|
|
34
|
+
end
|
|
27
35
|
end
|
|
28
36
|
end
|
|
@@ -1,36 +1,15 @@
|
|
|
1
1
|
module Gempilot
|
|
2
2
|
class Project
|
|
3
|
+
## The project's version file (+path+) and value. Bump arithmetic
|
|
4
|
+
## delegates to SegmentedVersion, so every bump moves strictly forward
|
|
5
|
+
## under RubyGems ordering.
|
|
3
6
|
Version = Data.define(:path, :value) do
|
|
4
7
|
def tag
|
|
5
8
|
"v#{value}"
|
|
6
9
|
end
|
|
7
10
|
|
|
8
11
|
def bump(segment = :patch)
|
|
9
|
-
|
|
10
|
-
when :major, :minor, :patch then bump_semver(segment)
|
|
11
|
-
when :dev then bump_dev
|
|
12
|
-
else raise ArgumentError, "Unknown segment #{segment.inspect}. Use :major, :minor, :patch, or :dev"
|
|
13
|
-
end
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
private
|
|
17
|
-
|
|
18
|
-
def bump_semver(segment)
|
|
19
|
-
major, minor, patch = value.split(".").first(3).map(&:to_i)
|
|
20
|
-
new_value = case segment.to_sym
|
|
21
|
-
when :major then "#{major + 1}.0.0"
|
|
22
|
-
when :minor then "#{major}.#{minor + 1}.0"
|
|
23
|
-
when :patch then "#{major}.#{minor}.#{patch + 1}"
|
|
24
|
-
end
|
|
25
|
-
with(value: new_value)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def bump_dev
|
|
29
|
-
if value.match?(/\.dev\d+\z/)
|
|
30
|
-
with(value: value.sub(/\d+\z/) { it.to_i + 1 })
|
|
31
|
-
else
|
|
32
|
-
with(value: "#{value}.dev1")
|
|
33
|
-
end
|
|
12
|
+
with(value: SegmentedVersion.parse(value).bump(segment).to_s)
|
|
34
13
|
end
|
|
35
14
|
|
|
36
15
|
alias_method :next_version, :bump
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
module Gempilot
|
|
2
|
+
## Arithmetic over the three version shapes gempilot accepts: release
|
|
3
|
+
## (+1.2.3+), tiny release (+1.2.3.1+), and dev prerelease (+1.2.3.dev1+).
|
|
4
|
+
##
|
|
5
|
+
## Every bump returns the smallest version of the requested shape that is
|
|
6
|
+
## strictly greater than the current version under RubyGems ordering, so
|
|
7
|
+
## bumping always moves a project forward: a dev bump previews the next
|
|
8
|
+
## patch (+1.2.3+ to +1.2.4.dev1+) and numeric bumps finalize a dev cycle
|
|
9
|
+
## (+1.2.4.dev2+ to +1.2.4+ for +:patch+).
|
|
10
|
+
SegmentedVersion = Data.define(:major, :minor, :patch, :tiny, :dev) do
|
|
11
|
+
def self.parse(string)
|
|
12
|
+
format = /\A(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?:\.(?<tiny>\d+)|\.dev(?<dev>\d+))?\z/
|
|
13
|
+
match = format.match(string)
|
|
14
|
+
raise ArgumentError, "cannot parse version: #{string.inspect}" unless match
|
|
15
|
+
|
|
16
|
+
new(**match.named_captures.to_h { |name, digits| [name.to_sym, digits&.to_i] })
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def bump(segment)
|
|
20
|
+
case segment.to_sym
|
|
21
|
+
when :major then bump_major
|
|
22
|
+
when :minor then bump_minor
|
|
23
|
+
when :patch then bump_patch
|
|
24
|
+
when :tiny then bump_tiny
|
|
25
|
+
when :dev then bump_dev
|
|
26
|
+
else raise ArgumentError, "Unknown segment #{segment.inspect}. Use :major, :minor, :patch, :tiny, or :dev"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_s
|
|
31
|
+
[major, minor, patch, tiny, ("dev#{dev}" if dev)].compact.join(".")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def bump_major
|
|
37
|
+
return finalize if dev && minor.zero? && patch.zero?
|
|
38
|
+
|
|
39
|
+
with(major: major + 1, minor: 0, patch: 0, tiny: nil, dev: nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def bump_minor
|
|
43
|
+
return finalize if dev && patch.zero?
|
|
44
|
+
|
|
45
|
+
with(minor: minor + 1, patch: 0, tiny: nil, dev: nil)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def bump_patch
|
|
49
|
+
dev ? finalize : with(patch: patch + 1, tiny: nil)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def bump_tiny
|
|
53
|
+
with(tiny: (tiny || 0) + 1, dev: nil)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def bump_dev
|
|
57
|
+
dev ? with(dev: dev + 1) : with(patch: patch + 1, tiny: nil, dev: 1)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def finalize
|
|
61
|
+
with(tiny: nil, dev: nil)
|
|
62
|
+
end
|
|
63
|
+
end.freeze
|
|
64
|
+
end
|
data/lib/gempilot/version.rb
CHANGED
|
@@ -50,7 +50,7 @@ module Gempilot
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def define_bump_task(project)
|
|
53
|
-
desc "Bump version (patch default,
|
|
53
|
+
desc "Bump version (patch default; segments: major, minor, patch, tiny, dev)"
|
|
54
54
|
task :bump, [:segment] do |_t, args|
|
|
55
55
|
segment = (args[:segment] || :patch).to_sym
|
|
56
56
|
old_version = project.version
|
data/vendor/vendored.gemv
CHANGED
|
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.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -72,6 +72,8 @@ executables:
|
|
|
72
72
|
extensions: []
|
|
73
73
|
extra_rdoc_files: []
|
|
74
74
|
files:
|
|
75
|
+
- ".claude/memory/be-decisive-not-menus.md"
|
|
76
|
+
- ".claude/memory/flagship-shared-rubocop-config-state.md"
|
|
75
77
|
- ".claude/skills/using-command-kit/SKILL.md"
|
|
76
78
|
- ".claude/skills/using-command-kit/cli-example.rb"
|
|
77
79
|
- ".claude/skills/using-command-kit/generator-pattern.rb"
|
|
@@ -107,7 +109,13 @@ files:
|
|
|
107
109
|
- data/templates/new/.keep
|
|
108
110
|
- data/templates/new/command.rb.erb
|
|
109
111
|
- docs/superpowers/plans/2026-07-20-release-task-hierarchy.md
|
|
112
|
+
- docs/superpowers/plans/2026-08-09-dev-version-bump.md
|
|
113
|
+
- docs/superpowers/plans/2026-09-23-command-generation-bootstrap.md
|
|
114
|
+
- docs/superpowers/plans/2026-09-23-land-betterleaks-jruby.md
|
|
115
|
+
- docs/superpowers/plans/2026-09-23-multi-gem-rubygems-push.md
|
|
116
|
+
- docs/superpowers/plans/2026-09-23-zeitwerk-task-inflector.md
|
|
110
117
|
- docs/superpowers/specs/2026-07-20-release-task-hierarchy-design.md
|
|
118
|
+
- docs/superpowers/specs/2026-08-09-dev-version-bump-design.md
|
|
111
119
|
- exe/gempilot
|
|
112
120
|
- issues.rec
|
|
113
121
|
- lib/core_ext/string/inflection_methods.rb
|
|
@@ -130,6 +138,7 @@ files:
|
|
|
130
138
|
- lib/gempilot/project.rb
|
|
131
139
|
- lib/gempilot/project/version.rb
|
|
132
140
|
- lib/gempilot/release_tasks.rb
|
|
141
|
+
- lib/gempilot/segmented_version.rb
|
|
133
142
|
- lib/gempilot/strict_shell.rb
|
|
134
143
|
- lib/gempilot/version.rb
|
|
135
144
|
- lib/gempilot/version_tag.rb
|
|
@@ -156,7 +165,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
156
165
|
- !ruby/object:Gem::Version
|
|
157
166
|
version: '0'
|
|
158
167
|
requirements: []
|
|
159
|
-
rubygems_version: 4.0.
|
|
168
|
+
rubygems_version: 4.0.22
|
|
160
169
|
specification_version: 4
|
|
161
170
|
summary: A toolkit for creating, managing, and releasing your own rubygems
|
|
162
171
|
test_files: []
|