gem-contribute 0.2.0 → 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 +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +14 -8
- data/.github/workflows/ci.yml +26 -0
- data/.github/workflows/pr-template-check.yml +100 -0
- data/.github/workflows/release.yml +71 -0
- data/CHANGELOG.md +38 -0
- data/CLAUDE.md +1 -1
- data/CONTRIBUTING.md +10 -4
- data/MAINTAINER.md +119 -2
- data/README.md +13 -1
- data/docs/OPEN_QUESTIONS.md +167 -0
- data/docs/ROADMAP.md +266 -0
- data/docs/adr/0006-standalone-gem-not-plugin.md +1 -1
- data/docs/adr/0008-rooibos-tui-framework.md +3 -3
- data/docs/adr/0010-charm-ruby-tui-framework.md +2 -2
- data/docs/adr/0011-host-adapter-owns-host-verbs.md +58 -0
- data/docs/adr/0012-output-free-service-objects-three-interface-architecture.md +79 -0
- data/docs/adr/0013-revert-to-rooibos.md +71 -0
- data/docs/adr/0014-ship-bundler-and-rubygems-plugins.md +75 -0
- data/docs/adr/README.md +7 -3
- data/docs/design-interface-layer.md +295 -0
- data/docs/design.md +31 -8
- data/docs/index.md +1 -1
- data/docs/prep-plan.md +6 -6
- data/lib/gem_contribute/cli/auth.rb +22 -44
- data/lib/gem_contribute/cli/config.rb +29 -15
- data/lib/gem_contribute/cli/fix.rb +122 -0
- data/lib/gem_contribute/cli/fork.rb +145 -0
- data/lib/gem_contribute/cli/init.rb +19 -24
- data/lib/gem_contribute/cli/issue_announcer.rb +42 -0
- data/lib/gem_contribute/cli/issues.rb +36 -47
- data/lib/gem_contribute/cli/platform_tools.rb +33 -0
- data/lib/gem_contribute/cli/post_clone_hooks.rb +50 -0
- data/lib/gem_contribute/cli/rate_limit_footer.rb +5 -3
- data/lib/gem_contribute/cli/scan.rb +20 -16
- data/lib/gem_contribute/cli/submit.rb +60 -64
- data/lib/gem_contribute/cli/workflow.rb +63 -0
- data/lib/gem_contribute/cli.rb +9 -16
- data/lib/gem_contribute/config.rb +27 -1
- data/lib/gem_contribute/git.rb +49 -0
- data/lib/gem_contribute/host_adapter.rb +52 -5
- data/lib/gem_contribute/host_adapters/github_adapter.rb +126 -37
- data/lib/gem_contribute/operations/announce.rb +52 -0
- data/lib/gem_contribute/operations/branch.rb +35 -0
- data/lib/gem_contribute/operations/clone.rb +41 -0
- data/lib/gem_contribute/operations/fix_pipeline.rb +70 -0
- data/lib/gem_contribute/operations/fork.rb +35 -0
- data/lib/gem_contribute/output/null.rb +20 -0
- data/lib/gem_contribute/output/standard.rb +71 -0
- data/lib/gem_contribute/version.rb +1 -1
- data/lib/gem_contribute.rb +10 -18
- metadata +115 -5
- data/lib/gem_contribute/cli/fork_clone_branch.rb +0 -204
data/docs/ROADMAP.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# Roadmap to v1
|
|
2
|
+
|
|
3
|
+
**Goal:** v1 of `gem-contribute` ships a single gem with three entry points against `github.com`:
|
|
4
|
+
|
|
5
|
+
1. **Standalone CLI** — `gem-contribute <verb>`. Bare invocation (no subcommand) launches the **Rooibos TUI** (project list → issue list → issue detail → CONTRIBUTING viewer + auth overlay).
|
|
6
|
+
2. **Bundler plugin** — `bundle contribute [verb]`. CLI-only. Bare invocation runs a default summary verb (TBD: `scan` or `list all`).
|
|
7
|
+
3. **RubyGems plugin** — `gem contribute [verb]`. CLI-only. Same shape as Bundler plugin.
|
|
8
|
+
|
|
9
|
+
All three share the same output-free service layer (per [ADR-0012](adr/0012-output-free-service-objects-three-interface-architecture.md)). All three are tested. v1 has a real release on rubygems.org, a CHANGELOG, and CI.
|
|
10
|
+
|
|
11
|
+
This document is the plan. Decisions still in flight live in [`OPEN_QUESTIONS.md`](OPEN_QUESTIONS.md) and get resolved one at a time.
|
|
12
|
+
|
|
13
|
+
> 🌱 marks a [good first issue](https://github.com/cdhagmann/gem-contribute/labels/good%20first%20issue) — small, self-contained scope, friendly for someone new to the codebase.
|
|
14
|
+
|
|
15
|
+
## Decision history (the short version)
|
|
16
|
+
|
|
17
|
+
- **Workshop is over** (2026-05-02). Decisions made primarily for workshop scope are reversed.
|
|
18
|
+
- **TUI framework: Rooibos** (per [ADR-0013](adr/0013-revert-to-rooibos.md), supersedes ADR-0010). Bubbletea-ruby was a workshop-driven choice; Rooibos enables the post-v1 world map view (issue #5) and matches the project's verbs better.
|
|
19
|
+
- **Three entry points, one gem** (per [ADR-0014](adr/0014-ship-bundler-and-rubygems-plugins.md), amends ADR-0006 and ADR-0012). Standalone binary + Bundler plugin + RubyGems plugin all live in the `gem-contribute` gem.
|
|
20
|
+
- **GitHub-only at v1.0.** GitLab/Codeberg adapters are v1.x territory. ADR-0011's architecture is the bet that pays off there.
|
|
21
|
+
- **Service layer is output-free** (per ADR-0012). dry-monads `Result` returns; dry-operation pipelines; no `stdout:` in operations.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What's already done
|
|
26
|
+
|
|
27
|
+
- **Data layer.** `LockfileParser`, `Resolver`, `GitHubAdapter`, `Auth`, `TokenStore`, `Cache`, `Operations::Fork`, `Operations::Clone`.
|
|
28
|
+
- **CLI verbs.** `init`, `scan`, `issues`, `config`, `auth`, `fork`, `fix`, `submit`.
|
|
29
|
+
- **HostAdapter cleanup.** ADR-0011 work landed: adapter owns host verbs, Operations layer composes them, CLI verbs compose Operations.
|
|
30
|
+
- **ADR-0012 service layer (Phase 1).** dry-monads `Result`, dry-operation pipelines, dry-initializer initializers, output-free `Operations::*`. Merged via [PR #48](https://github.com/cdhagmann/gem-contribute/pull/48) on 2026-05-04.
|
|
31
|
+
- **Basic CI.** rubocop + rspec on push/PR landed via [PR #21](https://github.com/cdhagmann/gem-contribute/pull/21) (closes [#7](https://github.com/cdhagmann/gem-contribute/issues/7)). Plugin-install smoke and gated integration tests still pending under [#43](https://github.com/cdhagmann/gem-contribute/issues/43).
|
|
32
|
+
|
|
33
|
+
## What hasn't started
|
|
34
|
+
|
|
35
|
+
- TUI
|
|
36
|
+
- Bundler plugin
|
|
37
|
+
- RubyGems plugin
|
|
38
|
+
- Remaining release infrastructure (MAINTAINER doc, release workflow, README rewrite)
|
|
39
|
+
|
|
40
|
+
## In flight
|
|
41
|
+
|
|
42
|
+
- **ADR-0012 Phase 2 (CLI output pipeline)** — `Output::Standard`/`Output::Null`, `tty-spinner`, `tty-prompt`. Open in [PR #51](https://github.com/cdhagmann/gem-contribute/pull/51).
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Phase 0 — Reset workshop-era decisions (DONE)
|
|
47
|
+
|
|
48
|
+
Two new ADRs landed:
|
|
49
|
+
|
|
50
|
+
- [ADR-0013](adr/0013-revert-to-rooibos.md) — Rooibos as the TUI framework, superseding ADR-0010.
|
|
51
|
+
- [ADR-0014](adr/0014-ship-bundler-and-rubygems-plugins.md) — Bundler + RubyGems plugins ship at v1, single gem.
|
|
52
|
+
|
|
53
|
+
ADR header sweep done in commit `00f5a4c`. Doc sweeps:
|
|
54
|
+
- [x] 🌱 [#23](https://github.com/cdhagmann/gem-contribute/issues/23) — Sweep `docs/design.md` for residual bubbletea references (no-op; doc was already clean)
|
|
55
|
+
- [x] 🌱 [#24](https://github.com/cdhagmann/gem-contribute/issues/24) — Sweep `docs/design-interface-layer.md` for "bubbletea" → "Rooibos" and update gem-plugin section
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Phase 1 — Service layer (ADR-0012 Phase 1) (DONE)
|
|
60
|
+
|
|
61
|
+
Made every operation output-free and Result-returning. This is what lets the TUI and the two plugins reuse the same code paths the standalone CLI uses. Merged via [PR #48](https://github.com/cdhagmann/gem-contribute/pull/48) on 2026-05-04.
|
|
62
|
+
|
|
63
|
+
**Steps:**
|
|
64
|
+
|
|
65
|
+
1. Add `dry-monads`, `dry-operation`, `dry-initializer` to gemspec.
|
|
66
|
+
2. Remove `stdout:` from `Operations::Fork` and `Operations::Clone`.
|
|
67
|
+
3. Define `Operations::Clone::Result = Data.define(:path, :reused)` (currently returns a bare path).
|
|
68
|
+
4. Convert both operations to return `Success(Result)` / `Failure(reason)`.
|
|
69
|
+
5. Convert `Workflow#build_adapter` to return `Success(adapter)` / `Failure(:unauthenticated)`.
|
|
70
|
+
6. Extract `Operations::Branch` (from inline branch logic in `CLI::Fix`) and `Operations::Announce` (from `CLI::IssueAnnouncer`).
|
|
71
|
+
7. Build `Operations::FixPipeline` using `dry-operation` to compose Fork → Clone → Branch → Announce.
|
|
72
|
+
8. Replace verbose initializers in `CLI::Fork`/`CLI::Fix` with `dry-initializer` (kills the rubocop suppressions).
|
|
73
|
+
9. Update CLI verbs to pattern-match on `Result`. Retire `Workflow#with_workflow_rescues`.
|
|
74
|
+
|
|
75
|
+
**Acceptance:**
|
|
76
|
+
- [x] No `Operations::*` class accepts `stdout:` or `stderr:`
|
|
77
|
+
- [x] All operations return `Success` / `Failure`
|
|
78
|
+
- [x] `FixPipeline` exists; `CLI::Fix#execute` calls it instead of wiring steps inline
|
|
79
|
+
- [x] All existing tests pass (no behaviour change visible to users)
|
|
80
|
+
- [x] No new rubocop suppressions
|
|
81
|
+
|
|
82
|
+
**Issues:**
|
|
83
|
+
- [x] [#25](https://github.com/cdhagmann/gem-contribute/issues/25) — Adopt dry-rb suite; convert `Operations::Fork`/`Clone` to `Result`
|
|
84
|
+
- [x] [#26](https://github.com/cdhagmann/gem-contribute/issues/26) — `Workflow#build_adapter` returns `Result`; retire `with_workflow_rescues`
|
|
85
|
+
- [x] [#27](https://github.com/cdhagmann/gem-contribute/issues/27) — Extract `Operations::Branch` and `Operations::Announce`; build `Operations::FixPipeline`
|
|
86
|
+
- [x] [#28](https://github.com/cdhagmann/gem-contribute/issues/28) — Migrate `CLI::Fork`/`CLI::Fix` initializers to dry-initializer
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Phase 2 — CLI pipeline (ADR-0012 Phase 2) (in flight)
|
|
91
|
+
|
|
92
|
+
Open in [PR #51](https://github.com/cdhagmann/gem-contribute/pull/51). Move CLI verbs to a semantic output abstraction so the look-and-feel can evolve independently of the service layer.
|
|
93
|
+
|
|
94
|
+
**Steps:**
|
|
95
|
+
|
|
96
|
+
1. Introduce `Output::Standard` (wraps stdout/stderr; `#info`, `#warn`, `#error`, `#progress`).
|
|
97
|
+
2. Introduce `Output::Null` (for tests).
|
|
98
|
+
3. Replace raw `@stdout` / `@stderr` in every CLI verb with `@output`.
|
|
99
|
+
4. Add `tty-spinner` for `Output::Standard#progress`.
|
|
100
|
+
5. Replace `CLI::Init`'s `stdout.print` + `@gets` with `tty-prompt`.
|
|
101
|
+
|
|
102
|
+
**Acceptance:**
|
|
103
|
+
- [ ] No raw `puts` to `@stdout`/`@stderr` in `lib/gem_contribute/cli/`
|
|
104
|
+
- [ ] Long operations show a spinner in TTY contexts and a plain line in non-TTY contexts
|
|
105
|
+
- [ ] `Init`'s test suite no longer mocks `gets`
|
|
106
|
+
- [ ] User-visible CLI output unchanged for non-interactive flows; spinners appear in interactive ones
|
|
107
|
+
|
|
108
|
+
**Issues:**
|
|
109
|
+
- [ ] [#29](https://github.com/cdhagmann/gem-contribute/issues/29) — `Output::Standard` and `Output::Null`; migrate CLI verbs off raw stdout/stderr
|
|
110
|
+
- [ ] [#30](https://github.com/cdhagmann/gem-contribute/issues/30) — `tty-spinner`-backed `#progress`
|
|
111
|
+
- [ ] [#31](https://github.com/cdhagmann/gem-contribute/issues/31) — `CLI::Init` uses `tty-prompt`
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Phase 3 — Rooibos TUI
|
|
116
|
+
|
|
117
|
+
**Umbrella issue:** [#2 — Implement Rooibos TUI on top of the v0.1 CLI](https://github.com/cdhagmann/gem-contribute/issues/2). The major work. Per design.md and ADR-0013.
|
|
118
|
+
|
|
119
|
+
**Pre-work (Q7 verification):**
|
|
120
|
+
- [ ] Confirm Rooibos's current published version on rubygems.org
|
|
121
|
+
- [ ] Verify `Command.http`, `Command.system`, `Command.wait`, `Command.cancel` still exist in 0.7.x
|
|
122
|
+
- [ ] Verify Rooibos snapshot test helpers still ship
|
|
123
|
+
- [ ] Pin `rooibos` and `ratatui_ruby` in gemspec
|
|
124
|
+
|
|
125
|
+
**Fragments:**
|
|
126
|
+
|
|
127
|
+
- `ProjectList` — gems from the lockfile with issue counts (lazy-loaded via `Command.http`)
|
|
128
|
+
- `IssueList` — open issues for the selected project, labels rendered verbatim (ADR-0005)
|
|
129
|
+
- `IssueDetail` — body, labels, action keys (`f` fix, `c` open CONTRIBUTING, `o` open in browser)
|
|
130
|
+
- `ContributingViewer` — rendered markdown (ADR-0007); also surfaces the upstream PR template per [#13](https://github.com/cdhagmann/gem-contribute/issues/13)
|
|
131
|
+
- `AuthOverlay` — device-flow prompt that fires on `:auth_required`
|
|
132
|
+
|
|
133
|
+
(World map fragment is post-v1; framework choice locks in now per ADR-0013.)
|
|
134
|
+
|
|
135
|
+
**Wiring:**
|
|
136
|
+
- [ ] `gem-contribute` (no args, with a `Gemfile.lock` in cwd) launches the TUI. This is the entry-point change in `cli.rb`.
|
|
137
|
+
- [ ] `gem-contribute` (no args, no `Gemfile.lock`) prints a clear "no Gemfile.lock found" message and the USAGE.
|
|
138
|
+
|
|
139
|
+
**Key contracts:**
|
|
140
|
+
- All async work goes through Rooibos Commands (no `Thread.new`, no `Async`)
|
|
141
|
+
- `Update` is a pure function tested as such (per fragment)
|
|
142
|
+
- Service-layer calls happen inside Commands and return `Result` types (Phase 1's contract)
|
|
143
|
+
- Command result messages are pattern-matched in `Update` to `Success(...)` / `Failure(...)` shapes
|
|
144
|
+
|
|
145
|
+
**Acceptance:**
|
|
146
|
+
- [ ] All five fragments render and route as designed
|
|
147
|
+
- [ ] `Update` test for every fragment, covering each key handler and each command-result branch
|
|
148
|
+
- [ ] At least one snapshot test for the main flow (project list → issue list → issue detail → fix)
|
|
149
|
+
- [ ] At least one snapshot test for the auth overlay firing mid-flow
|
|
150
|
+
- [ ] `q` quits, `Ctrl+C` quits, `?` shows help overlay
|
|
151
|
+
- [ ] Status bar shows rate limit remaining
|
|
152
|
+
|
|
153
|
+
**Issues (under umbrella [#2](https://github.com/cdhagmann/gem-contribute/issues/2)):**
|
|
154
|
+
- [ ] [#32](https://github.com/cdhagmann/gem-contribute/issues/32) — `ProjectList` fragment with lazy-loaded issue counts
|
|
155
|
+
- [ ] [#33](https://github.com/cdhagmann/gem-contribute/issues/33) — `IssueList` fragment
|
|
156
|
+
- [ ] [#34](https://github.com/cdhagmann/gem-contribute/issues/34) — `IssueDetail` fragment with action keys (f / c / o)
|
|
157
|
+
- [ ] [#35](https://github.com/cdhagmann/gem-contribute/issues/35) — `ContributingViewer` fragment (may absorb [#13](https://github.com/cdhagmann/gem-contribute/issues/13))
|
|
158
|
+
- [ ] [#36](https://github.com/cdhagmann/gem-contribute/issues/36) — `AuthOverlay` fragment for device-flow prompt
|
|
159
|
+
- [ ] [#37](https://github.com/cdhagmann/gem-contribute/issues/37) — No-arg `gem-contribute` launches the TUI
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Phase 4 — Bundler plugin (`bundle contribute`)
|
|
164
|
+
|
|
165
|
+
A `plugins.rb` entry point at the root of the gem registers a Bundler plugin command per Bundler convention. Delegates to the same dispatch table the standalone CLI uses.
|
|
166
|
+
|
|
167
|
+
**Constraints:**
|
|
168
|
+
- Plugin entry point MUST NOT require Rooibos or `ratatui_ruby` (per ADR-0014). TUI loading is gated to the standalone binary.
|
|
169
|
+
- Bare `bundle contribute` runs the default verb (TBD per OPEN_QUESTIONS Q3a: `scan` or `list all`).
|
|
170
|
+
- `bundle contribute <verb>` mirrors `gem-contribute <verb>`.
|
|
171
|
+
|
|
172
|
+
**Acceptance:**
|
|
173
|
+
- [ ] `bundle plugin install gem-contribute` works against the local gem
|
|
174
|
+
- [ ] `bundle contribute` produces the default summary
|
|
175
|
+
- [ ] `bundle contribute fix sidekiq/123` runs the fix verb
|
|
176
|
+
- [ ] Smoke test verifies plugin registration without booting the TUI
|
|
177
|
+
|
|
178
|
+
**Issue:** [#38](https://github.com/cdhagmann/gem-contribute/issues/38) — Bundler plugin: `bundle contribute` entry point
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Phase 5 — RubyGems plugin (`gem contribute`)
|
|
183
|
+
|
|
184
|
+
A `rubygems_plugin.rb` entry point registers a `Gem::Command` subclass per RubyGems convention. Same dispatch table.
|
|
185
|
+
|
|
186
|
+
**Constraints:**
|
|
187
|
+
- Same TUI-load gating as Phase 4.
|
|
188
|
+
- Same default-verb behavior as Phase 4.
|
|
189
|
+
|
|
190
|
+
**Acceptance:**
|
|
191
|
+
- [ ] `gem install gem-contribute` registers the `Gem::Command`
|
|
192
|
+
- [ ] `gem contribute --help` lists the same verbs as `gem-contribute --help`
|
|
193
|
+
- [ ] `gem contribute fix sidekiq/123` runs the fix verb
|
|
194
|
+
- [ ] Smoke test verifies plugin registration without booting the TUI
|
|
195
|
+
|
|
196
|
+
**Issue:** [#39](https://github.com/cdhagmann/gem-contribute/issues/39) — RubyGems plugin: `gem contribute` Gem::Command
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Phase 6 — Polish, release infrastructure, v1.0
|
|
201
|
+
|
|
202
|
+
Everything required to call it 1.0 and not 0.x.
|
|
203
|
+
|
|
204
|
+
**Pre-existing user-facing issues that fold into this phase:**
|
|
205
|
+
- [ ] 🌱 [#1 — Add `preferred_labels` config so non-canonical good-first-issue labels are caught](https://github.com/cdhagmann/gem-contribute/issues/1)
|
|
206
|
+
- [ ] 🌱 [#3 — Add `gem-contribute open <gem>` to open the repo in the browser](https://github.com/cdhagmann/gem-contribute/issues/3)
|
|
207
|
+
- [ ] 🌱 [#9 — Add `--label LABEL` flag to scan and issues for one-off overrides](https://github.com/cdhagmann/gem-contribute/issues/9) (related to #1)
|
|
208
|
+
- [ ] 🌱 [#10 — Friendlier message when `fix` runs against a repo you own](https://github.com/cdhagmann/gem-contribute/issues/10)
|
|
209
|
+
|
|
210
|
+
**Release infrastructure:**
|
|
211
|
+
- [ ] 🌱 [#40](https://github.com/cdhagmann/gem-contribute/issues/40) — Add CHANGELOG.md
|
|
212
|
+
- [ ] 🌱 [#41](https://github.com/cdhagmann/gem-contribute/issues/41) — Add CONTRIBUTING.md
|
|
213
|
+
- [ ] [#42](https://github.com/cdhagmann/gem-contribute/issues/42) — Add MAINTAINER.md (release process, OAuth App, plugin verification)
|
|
214
|
+
- [ ] OAuth App: stay on personal-account App for v1.0 (per Q13); migrate when rate limits bite
|
|
215
|
+
- [ ] [#43](https://github.com/cdhagmann/gem-contribute/issues/43) — CI workflow (`.github/workflows/ci.yml`): rubocop + rspec; gated integration tests; plugin install smoke (basic rubocop + rspec already landed via [PR #21](https://github.com/cdhagmann/gem-contribute/pull/21) / [#7](https://github.com/cdhagmann/gem-contribute/issues/7); gated integration tests and plugin smoke still pending)
|
|
216
|
+
- [x] [#44](https://github.com/cdhagmann/gem-contribute/issues/44) — Release workflow (`.github/workflows/release.yml`) with **Trusted Publishing (OIDC)** (will go live with the 0.4 release)
|
|
217
|
+
- [ ] 🌱 [#45](https://github.com/cdhagmann/gem-contribute/issues/45) — Archive workshop docs to `docs/archive/`
|
|
218
|
+
- [ ] [#46](https://github.com/cdhagmann/gem-contribute/issues/46) — README rewrite for v1 audience
|
|
219
|
+
- [ ] Verify `bundle plugin install` and `gem install` from a clean machine (covered by CI smoke test in #43)
|
|
220
|
+
- [ ] [#47](https://github.com/cdhagmann/gem-contribute/issues/47) — Meta-PR: use `gem-contribute` against a real downstream
|
|
221
|
+
- [ ] Tag `v1.0.0`, push to rubygems
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Sequencing logic
|
|
226
|
+
|
|
227
|
+
- **Phase 0 → 1 → 2 are strictly ordered.** Each unblocks the next.
|
|
228
|
+
- **Phase 3, 4, 5 can in principle parallelize** once Phases 1–2 land. In practice Phase 3 (TUI) is the biggest piece and probably ships first; the plugin shims (Phases 4–5) are small once the dispatch table is the single source of truth.
|
|
229
|
+
- **Phase 6 happens after** all of 3/4/5 work end-to-end.
|
|
230
|
+
|
|
231
|
+
If we're behind: Phase 3 is the load-bearing one for "v1 worth releasing." Phases 4–5 can slip to v1.1 if needed (they unlock the better-discoverability story but don't change capability). Phase 6 cannot slip.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Out of scope at v1.0
|
|
236
|
+
|
|
237
|
+
(Confirmed via OPEN_QUESTIONS Q10.)
|
|
238
|
+
|
|
239
|
+
- Multi-host adapters — v1.x. GitLab tracked at [#8](https://github.com/cdhagmann/gem-contribute/issues/8); Codeberg/sourcehut not yet ticketed.
|
|
240
|
+
- gem.coop-exclusive gems — v1.x. Mirrored gems work today; dedicated-namespace gems need a Resolver fallback to the gem.coop API. Tracked at [#50](https://github.com/cdhagmann/gem-contribute/issues/50).
|
|
241
|
+
- World-map TUI fragment — post-v1, awaits adoption. Tracked indirectly via [#5](https://github.com/cdhagmann/gem-contribute/issues/5)'s acceptance criteria (which also owns the `KICKED_THE_TIRES.yml` data source). [needs dedicated issue when ready to build]
|
|
242
|
+
- Private repos / `repo` OAuth scope — post-v1, no issue
|
|
243
|
+
- PR creation from inside the TUI — design choice, browser-based stays (ADR-0011)
|
|
244
|
+
- AI-anything (ADR-0007)
|
|
245
|
+
- Label normalization (ADR-0005)
|
|
246
|
+
- CONTRIBUTING parsing (ADR-0007)
|
|
247
|
+
|
|
248
|
+
🌱 [#5](https://github.com/cdhagmann/gem-contribute/issues/5) itself stays open indefinitely as a sandbox for new contributors to practice the `fix` → `submit` loop.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
## Issue tracking
|
|
253
|
+
|
|
254
|
+
All roadmap work is tracked on the issue tracker. Filter by `phase:N` label to see a phase's work, e.g. [phase:1](https://github.com/cdhagmann/gem-contribute/labels/phase%3A1).
|
|
255
|
+
|
|
256
|
+
| Phase | Issue range | Notes |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| 0 | #23, #24 | Two doc sweeps |
|
|
259
|
+
| 1 | #25–#28 | Service layer (ADR-0012) |
|
|
260
|
+
| 2 | #29–#31 | CLI output pipeline |
|
|
261
|
+
| 3 | umbrella #2 + sub-issues #32–#37, plus #13 | Five fragments + launch wiring |
|
|
262
|
+
| 4 | #38 | Bundler plugin |
|
|
263
|
+
| 5 | #39 | RubyGems plugin |
|
|
264
|
+
| 6 | #40–#47, plus #1, #3, #9, #10 | Release infra + pre-existing polish |
|
|
265
|
+
|
|
266
|
+
Out-of-scope items don't get phase labels. [#5](https://github.com/cdhagmann/gem-contribute/issues/5) (sandbox) and [#8](https://github.com/cdhagmann/gem-contribute/issues/8) (GitLab adapter, v1.x) live without phase labels.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ADR 0006: Standalone gem, not a Bundler plugin
|
|
2
2
|
|
|
3
|
-
**Status:**
|
|
3
|
+
**Status:** Partially superseded — the standalone-gem decision stands; the no-Bundler-plugin decision is **reversed by [ADR-0014](0014-ship-bundler-and-rubygems-plugins.md)**. Earlier amendment by [ADR-0012](0012-output-free-service-objects-three-interface-architecture.md) (which added a RubyGems plugin) is now itself superseded by ADR-0014's single-gem packaging.
|
|
4
4
|
**Date:** 2026-04-27
|
|
5
5
|
|
|
6
6
|
## Context
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ADR 0008: Use Rooibos for the TUI layer
|
|
2
2
|
|
|
3
|
-
**Status:** Superseded by [ADR-0010](0010-charm-ruby-tui-framework.md)
|
|
3
|
+
**Status:** Superseded by [ADR-0010](0010-charm-ruby-tui-framework.md). **Substance subsequently restored by [ADR-0013](0013-revert-to-rooibos.md)**, which superseded ADR-0010 — Rooibos is once again the TUI framework, with new framing. Read this ADR for the original technical reasoning; read ADR-0013 for the current decision header.
|
|
4
4
|
**Date:** 2026-04-27
|
|
5
5
|
**Supersedes parts of:** the original "TUI built directly on `ratatui_ruby`" approach implied by ADR-0001 and the `docs/design.md` v1.
|
|
6
6
|
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
`gem-contribute` is a TUI that needs to:
|
|
10
10
|
|
|
11
11
|
1. Make multiple HTTP calls (RubyGems API, GitHub API, GitHub OAuth device flow polling) without freezing the UI.
|
|
12
|
-
2. Shell out to `git` for
|
|
12
|
+
2. Shell out to `git` for the `fix` flow, also without freezing the UI.
|
|
13
13
|
3. Compose four primary views (project list → issues → issue detail → CONTRIBUTING) with the auth-prompt flow able to interrupt any of them.
|
|
14
14
|
4. Be testable enough that the gem can be maintained past the conference without breaking on every PR.
|
|
15
15
|
|
|
@@ -41,7 +41,7 @@ Use Rooibos as the TUI framework. `ratatui_ruby` remains a transitive dependency
|
|
|
41
41
|
|
|
42
42
|
## Consequences
|
|
43
43
|
|
|
44
|
-
**On the design doc:** the "Modules" section needs revision. Views become Rooibos fragments, not bare classes. The `Worker` module disappears —
|
|
44
|
+
**On the design doc:** the "Modules" section needs revision. Views become Rooibos fragments, not bare classes. The `Worker` module disappears — `fix` is a sequence of Commands emitted from `Update`. The architecture diagram becomes MVU-shaped. Testing strategy shifts from "test the boundaries, skip the TUI" to "test the Update functions everywhere."
|
|
45
45
|
|
|
46
46
|
**On dependencies:** add `rooibos` to the gemspec. Pin to `~> 0.7.0` for v0.1 (allows patch updates within 0.7, blocks 0.8+ until we audit). Bump deliberately, with an ADR if the bump requires meaningful changes.
|
|
47
47
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# ADR 0010: Use Charm-Ruby (bubbletea + lipgloss) for the TUI layer
|
|
2
2
|
|
|
3
|
-
**Status:**
|
|
3
|
+
**Status:** **Superseded by [ADR-0013](0013-revert-to-rooibos.md)** (2026-05-03). Bubbletea-ruby was a workshop-driven choice; once the workshop concluded, ADR-0013 restored Rooibos as the TUI framework.
|
|
4
4
|
**Date:** 2026-05-02
|
|
5
|
-
**Supersedes:** [ADR-0008](0008-rooibos-tui-framework.md)
|
|
5
|
+
**Supersedes:** [ADR-0008](0008-rooibos-tui-framework.md) — but see ADR-0013, which reverses this supersession.
|
|
6
6
|
|
|
7
7
|
## Context
|
|
8
8
|
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# ADR 0011: HostAdapter owns host verbs; Operations compose them; CLI verbs compose Operations
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-05-02
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
`CLI::Fork` (and, by delegation, `CLI::Fix`) currently fuses four concerns into one class:
|
|
9
|
+
|
|
10
|
+
1. CLI shell — argv parsing, usage errors, summary, post-clone hooks.
|
|
11
|
+
2. Host-API ceremony — call `adapter.fork`, poll `adapter.fork_ready?` in a 12×5s loop, branch on `adapter.already_forked?`.
|
|
12
|
+
3. VCS work on the local filesystem — `git clone` into `<root>/<owner>/<repo>`, reuse if `.git` exists, add an `upstream` remote.
|
|
13
|
+
4. Host-specific URL templating — `https://github.com/<owner>/<repo>.git` is hardcoded at [fork.rb:51](../../lib/gem_contribute/cli/fork.rb).
|
|
14
|
+
|
|
15
|
+
ADR-0001 (and the design doc) already commit to GitLab and Codeberg adapters as a near-term goal. The hardcoded `github.com` literal and the GitHub-shaped readiness loop both block that. A previous refactor (commit 3d53ffc, "dissolve ForkClone into Fork") pulled in the opposite direction: it merged the bootstrap primitive into the CLI verb. That made the multi-host port harder, not easier.
|
|
16
|
+
|
|
17
|
+
Two reasonable shapes for splitting:
|
|
18
|
+
|
|
19
|
+
1. **Wider adapter, thicker CLI verbs.** Push everything host-specific into `HostAdapter` (`fork`, `comment`, `pull_request_url`, `clone_url`); CLI verbs compose adapter calls and `Git` calls directly.
|
|
20
|
+
2. **Adapter + Operations layer.** Same wider adapter, plus a thin layer of host-agnostic primitives (`Operations::Fork`, `Operations::Clone`) that compose `HostAdapter` and `Git`. CLI verbs compose Operations.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
Adopt shape 2: a three-layer split.
|
|
25
|
+
|
|
26
|
+
- **`HostAdapter`** owns *every* host-API verb. Concrete methods: `fork`, `comment`, `pull_request_url`, `clone_url`, plus the existing reads (`issues`, `issue`, `issue_comments`, `community_profile`, `file_contents`, `search_issues`) and identity (`viewer_login`).
|
|
27
|
+
- **`Operations::Fork`** and **`Operations::Clone`** are the bootstrap primitives. They depend on a `HostAdapter` and (for Clone) a `Git`. They produce the local clone path the CLI verbs need.
|
|
28
|
+
- **`CLI::Fork`** and **`CLI::Fix`** parse argv, resolve a `Project`, compose the Operations primitives, print summaries, and run post-clone hooks. Nothing else.
|
|
29
|
+
|
|
30
|
+
Three sub-decisions that shape the adapter's surface:
|
|
31
|
+
|
|
32
|
+
- **`fork(project)` is idempotent and blocks until ready.** The 12×5s polling loop moves *into* the GitHub adapter. Callers ask for "fork this and give me a working clone URL"; the adapter decides whether that needs a poll, a single request, or something else. `fork_ready?` and `already_forked?` become private details.
|
|
33
|
+
- **PR creation stays browser-based; the adapter exposes `pull_request_url(...)`.** Today `submit` deliberately opens a pre-filled compare page so the user reviews PR text before submitting. We keep that UX. The adapter's job is to construct the host-correct compare URL; GitLab returns a `merge_requests/new` URL, GitHub returns a `compare` URL, etc.
|
|
34
|
+
- **`clone_url(project)` replaces the hardcoded `https://github.com/...` literal.** The Operations layer asks the adapter; the adapter knows its own host.
|
|
35
|
+
|
|
36
|
+
## Reasoning
|
|
37
|
+
|
|
38
|
+
The data layer / TUI layer split in [`docs/design.md`](../design.md) is built around adapters being swappable. That bet pays off only if "swap in a GitLab adapter" really is a weekend project. Today it isn't — a GitLab port would have to fork (no pun intended) the GitHub-shaped readiness loop, the hardcoded clone URL, and the same-shape compare URL out of `CLI::Fork` and `CLI::Submit`. With this split, a GitLab port is: implement `HostAdapter#fork` (likely no poll), `#clone_url` (return `https://gitlab.com/...`), `#pull_request_url` (return the GitLab MR URL form), `#comment`. Operations and CLI don't change.
|
|
39
|
+
|
|
40
|
+
The Operations layer (rather than a fatter CLI) earns its keep because two things in the bootstrap aren't host-API and aren't raw git either: the *clone-or-reuse* policy (skip clone if `.git` exists) and the *upstream remote* policy (always add `upstream` pointing at the canonical repo's `clone_url`). Those are gem-contribute conventions, not git primitives, and they're shared between `fix` and `fork`. Putting them in their own class makes them testable in isolation and keeps the CLI verbs honestly thin.
|
|
41
|
+
|
|
42
|
+
This direction supersedes the merge in 3d53ffc. That commit was right that `ForkClone` and `Fork` had drifted into near-duplicates; it was wrong that the resolution was to dissolve the primitive into the verb. The correct resolution was to *re-extract* the primitive at a sharper boundary — which is what this ADR does.
|
|
43
|
+
|
|
44
|
+
## Alternatives considered
|
|
45
|
+
|
|
46
|
+
- **Wider adapter, no Operations layer (shape 1 above).** Simpler — one fewer namespace. Rejected because the clone-or-reuse and upstream-remote policies don't belong on `HostAdapter` (they're not host-API) or on `Git` (they're gem-contribute policy on top of git). Without an Operations layer they leak into CLI verbs, where they get duplicated between `fork` and `fix`.
|
|
47
|
+
- **Move PR creation to API-based (`adapter.create_pull_request`).** Rejected — the deliberate UX in [`submit.rb`](../../lib/gem_contribute/cli/submit.rb) is that the user reviews PR text in the browser before submitting. That's a v1 product decision, not an artifact of laziness.
|
|
48
|
+
- **Keep `fork_ready?` / `already_forked?` on the public adapter interface.** Rejected as the default. They're GitHub-shaped (the 202-then-poll dance is a GitHub artifact). Hiding them behind an idempotent, blocking `fork` lets each host implement readiness however it actually works. We can re-expose them later if a real caller needs them.
|
|
49
|
+
|
|
50
|
+
## Consequences
|
|
51
|
+
|
|
52
|
+
- `HostAdapter` grows: `fork` semantics tighten (idempotent, blocking), `comment` replaces `comment_on_issue`, new methods `pull_request_url` and `clone_url`. `fork_ready?` and `already_forked?` come off the public interface.
|
|
53
|
+
- New namespace `GemContribute::Operations::` housing `Fork` and `Clone`.
|
|
54
|
+
- `CLI::Fork` and `CLI::Fix` shrink. The `ensure_fork` / `wait_until_ready` / `clone_into_root` privates move out.
|
|
55
|
+
- The hardcoded `github.com` URL literal at [fork.rb:51](../../lib/gem_contribute/cli/fork.rb) goes away.
|
|
56
|
+
- [`docs/design.md`](../design.md) needs the `HostAdapter` signature snippet (currently shows the old shape) and a paragraph on the Operations layer.
|
|
57
|
+
- Tests: `Operations::Fork` and `Operations::Clone` get unit specs; the existing `CLI::Fork` and `CLI::Fix` specs shrink (less to assert in the verb itself, more in the primitives).
|
|
58
|
+
- The "no orchestrator class" rule in CLAUDE.md is unchanged: Operations primitives are not orchestrators. They're single-step compositions of an adapter call and (sometimes) a git call. The TUI's `fix` flow remains a state machine in `Update`.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# ADR 0012: Output-free service objects, dry-monads Result contract, three-interface architecture
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted — partially amended by [ADR-0014](0014-ship-bundler-and-rubygems-plugins.md). The output-free service-layer / dry-monads / dry-operation contract stands. Packaging is changed: ADR-0014 collapses the planned separate `rubygems-contribute` gem into a single `gem-contribute` gem with three entry points, and adds a Bundler plugin alongside.
|
|
4
|
+
**Date:** 2026-05-03
|
|
5
|
+
**Amends:** [ADR-0006](0006-standalone-gem-not-plugin.md) — adds a RubyGems plugin as a third interface; the Bundler plugin decision was unchanged at the time of this ADR (subsequently reversed by ADR-0014).
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
`Operations::Fork` and `Operations::Clone` accept `stdout:` and print progress lines as side effects during `call`. This couples service objects to a specific output model and breaks down across the three interfaces now in scope:
|
|
10
|
+
|
|
11
|
+
- **CLI** (`gem-contribute`): stdout/stderr strings, synchronous.
|
|
12
|
+
- **TUI** (bubbletea, ADR-0010): no output stream — results travel as messages delivered by Commands to `Update`, which renders model state.
|
|
13
|
+
- **gem plugin** (`gem contribute`): a RubyGems plugin (not a Bundler plugin — see ADR-0006) that reuses the CLI pipeline but is a distinct interface layer.
|
|
14
|
+
|
|
15
|
+
Injecting an `Output::Tui` into service objects would require it to dispatch callbacks back into bubbletea's async event loop from a synchronous call site inside a Command. That is complex, depends on unverified bubbletea-ruby API surface (ADR-0010 flagged this explicitly), and routes information through a mechanism it doesn't belong in.
|
|
16
|
+
|
|
17
|
+
Separately, several places in the codebase signal failure by returning `nil` and printing to `stderr` as a side effect (notably `Workflow#build_adapter`). This mixes concerns: the service layer decides what to say, and callers are left checking `nil` without knowing why.
|
|
18
|
+
|
|
19
|
+
Workshop constraints (Blue Ridge Ruby 2026) that previously argued against dry-rb ecosystem dependencies no longer apply — the workshop concluded 2026-05-02.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
1. **Service objects are output-free.** `Operations::*` and all data-layer classes accept no `stdout:` or `stderr:` parameter and produce no I/O side effects.
|
|
24
|
+
|
|
25
|
+
2. **Service objects return `dry-monads` `Result` types.** `Success(value)` on the happy path, `Failure(reason)` for expected error conditions. Typed exceptions (`AuthRequired`, `AdapterError`) are no longer used as cross-layer control flow; they may still be raised and rescued within a single layer.
|
|
26
|
+
|
|
27
|
+
3. **Multi-step pipelines use `dry-operation`.** The fork → clone → branch → announce sequence in `fix` is expressed as a `dry-operation` pipeline: each step receives and enriches a shared input, and failure at any step short-circuits the chain.
|
|
28
|
+
|
|
29
|
+
4. **Three interface layers share service objects and own their own output.**
|
|
30
|
+
- CLI: prints around service calls using `Output::Standard` (wraps `stdout`/`stderr`; exposes `#info`, `#warn`, `#error`).
|
|
31
|
+
- TUI: wraps service calls in Commands; `Update` renders the returned `Success`/`Failure` as model state.
|
|
32
|
+
- gem plugin: reuses the CLI pipeline; does not launch the TUI.
|
|
33
|
+
|
|
34
|
+
5. **`Output::Standard` and `Output::Null` live in the interface layer only.** Service objects never see them.
|
|
35
|
+
|
|
36
|
+
## Reasoning
|
|
37
|
+
|
|
38
|
+
**Output-free is the only shape that works across all three interfaces.** The TUI has no output stream — injecting one requires plumbing that doesn't exist yet (bubbletea-ruby's callback story is unverified). Output-free service objects with Result return types leave each interface free to handle output in the way natural to it.
|
|
39
|
+
|
|
40
|
+
**`dry-monads` Result over exceptions for cross-layer signaling.** Auth failure and adapter errors are expected outcomes, not exceptional conditions — the adapter will regularly encounter them on the happy path (rate limits, unauthenticated users, forks that already exist). `Failure(:unauthenticated)` makes the call site enumerate every outcome explicitly rather than knowing which exceptions to rescue. Ruby 3.2 `case/in` pattern matching on `Success`/`Failure` is readable and idiomatic.
|
|
41
|
+
|
|
42
|
+
**`dry-operation` over manual step composition.** The fix pipeline currently threads state through sequential calls with early returns on `nil`. `dry-operation` names each step, makes its `Success`/`Failure` contract explicit, and allows testing each step in isolation. Adding `dry-operation` pulls in `dry-monads` transitively, so one gemspec entry covers both.
|
|
43
|
+
|
|
44
|
+
**RubyGems plugin is a distinct interface, not covered by ADR-0006.** ADR-0006 rejected the *Bundler* plugin pattern (`bundle contribute`). A RubyGems plugin (`gem contribute`) registers a `Gem::Command` in a gem named `rubygems-contribute` — a different mechanism, not considered in ADR-0006. The three-interface architecture creates a natural home for it: it reuses the CLI pipeline without touching the TUI.
|
|
45
|
+
|
|
46
|
+
## Alternatives considered
|
|
47
|
+
|
|
48
|
+
- **`Output` abstraction injected into service objects** (`Output::Standard`, `Output::Tui`). Rejected: `Output::Tui` requires a dispatch callback into bubbletea's event loop from a synchronous call site inside an async Command. Complex, unverified, routes output through the wrong abstraction.
|
|
49
|
+
|
|
50
|
+
- **Keep `stdout:` injection; pass a null output to the TUI.** Rejected: silently drops progress information that the TUI should surface as model state. Information is lost rather than translated.
|
|
51
|
+
|
|
52
|
+
- **`dry-transaction` instead of `dry-operation`.** Rejected: `dry-transaction` is deprecated by the dry-rb team. `dry-operation` is the current recommendation with the same step-composition semantics.
|
|
53
|
+
|
|
54
|
+
- **Native Ruby only (`Data.define` results + typed exceptions + `case/in`).** Valid; Ruby 3.2 has most of the surface. Rejected now that workshop constraints are lifted: `dry-monads` provides a richer failure vocabulary, Do notation reduces boilerplate in multi-step callers, and `dry-operation` formalizes pipeline shape more explicitly than manual early-returns.
|
|
55
|
+
|
|
56
|
+
## Consequences
|
|
57
|
+
|
|
58
|
+
**On `Operations::Fork` and `Operations::Clone`:** remove `stdout:`. Both return `Success(Result)` or `Failure(reason)`. `Operations::Clone::Result` gains a `reused:` field (mirroring `Operations::Fork::Result`) so CLI callers can print the appropriate message without asking the operation what happened.
|
|
59
|
+
|
|
60
|
+
**On `Workflow#build_adapter`:** remove `nil`-returning and stderr side effect. Return `Success(adapter)` or `Failure(:unauthenticated)`. Callers pattern-match.
|
|
61
|
+
|
|
62
|
+
**On `CLI::Fork#execute` and `CLI::Fix#execute`:** print progress and results around service calls using `Output::Standard` rather than raw `@stdout`/`@stderr`.
|
|
63
|
+
|
|
64
|
+
**On `CLI::Fix`:** the fork → clone → branch → announce sequence becomes a `dry-operation` pipeline.
|
|
65
|
+
|
|
66
|
+
**On dependencies:** add `dry-operation` to the gemspec (verify current version on rubygems.org before pinning; `~> 0.1` at time of writing). `dry-monads` is pulled in transitively but may be listed explicitly for clarity.
|
|
67
|
+
|
|
68
|
+
**On the gem plugin:** a future `rubygems-contribute` gem registers a `Gem::Command` and delegates to the CLI pipeline. This ADR establishes its architectural home; a separate ADR is not required for its internal structure.
|
|
69
|
+
|
|
70
|
+
**On ADR-0006:** status updated to note that the Bundler plugin decision is unchanged, but a RubyGems plugin interface is now explicitly in scope under ADR-0012.
|
|
71
|
+
|
|
72
|
+
**On `CLAUDE.md`:** the working-agreement bullet "Async work is always a bubbletea Command" gains a companion: "Service objects return `dry-monads` `Result` types and produce no output."
|
|
73
|
+
|
|
74
|
+
## What this doesn't change
|
|
75
|
+
|
|
76
|
+
- ADR-0001 (just-in-time auth). The auth flow's shape is unchanged; the signaling mechanism moves from exception to `Failure`.
|
|
77
|
+
- ADR-0002 through ADR-0005 (data layer). Parsers and resolvers are already output-free.
|
|
78
|
+
- ADR-0010 (bubbletea + lipgloss). Framework choice unchanged; this ADR defines the contract those Commands return.
|
|
79
|
+
- ADR-0011 (HostAdapter owns host verbs). The adapter interface is unchanged; its error conditions now propagate as `Failure` rather than raised exceptions at the cross-layer boundary.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# ADR 0013: Revert TUI framework to Rooibos
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-05-03
|
|
5
|
+
**Supersedes:** [ADR-0010](0010-charm-ruby-tui-framework.md)
|
|
6
|
+
**Restores substance of:** [ADR-0008](0008-rooibos-tui-framework.md) (with a new framing)
|
|
7
|
+
|
|
8
|
+
## Context
|
|
9
|
+
|
|
10
|
+
[ADR-0010](0010-charm-ruby-tui-framework.md) (2026-05-02) chose bubbletea-ruby + lipgloss-ruby over Rooibos. Two reasons drove that decision:
|
|
11
|
+
|
|
12
|
+
1. **Workshop onboarding cost.** Rooibos's lambda-as-constant style (`Init = ->`, `View = ->`) was identified in ADR-0008 itself as unfamiliar to Rails developers. ADR-0010 cited this as a real cost for Blue Ridge Ruby 2026 attendees.
|
|
13
|
+
2. **Idiomatic Ruby surface.** Bubbletea-ruby's class/mixin shape (`include Bubbletea::Model`) was framed as a more natural Ruby surface for the workshop audience.
|
|
14
|
+
|
|
15
|
+
The Blue Ridge Ruby workshop concluded 2026-05-02. ADR-0010's primary justification is no longer load-bearing. At the same time, a new feature has entered the v1 roadmap: a near-easter-egg "world map" view ([issue #5](https://github.com/cdhagmann/gem-contribute/issues/5)) showing the locations of users who've kicked the tires on the tool. The feature itself ships post-v1 (until adoption is large enough for the data to be interesting), but the framework choice locks in now.
|
|
16
|
+
|
|
17
|
+
Stage 3 (the TUI work) has not started. The cost of changing the framework decision is at its lifetime minimum.
|
|
18
|
+
|
|
19
|
+
## Decision
|
|
20
|
+
|
|
21
|
+
Use **Rooibos** as the TUI framework. `ratatui_ruby` is the rendering layer Rooibos sits on top of. This is the framework choice originally made in ADR-0008.
|
|
22
|
+
|
|
23
|
+
## Reasoning
|
|
24
|
+
|
|
25
|
+
**The workshop-onboarding argument is gone.** The single biggest reason ADR-0010 gave for switching no longer applies. The lambda-as-constant style is a mild stylistic cost for any Ruby developer; it's not a structural barrier. Maintainers and contributors going forward will be self-selected developers who chose to work on this codebase, not workshop attendees parachuted in for a weekend.
|
|
26
|
+
|
|
27
|
+
**Preserving the world-map view as a future option.** Rooibos sits on `ratatui_ruby`, which exposes the full Ratatui widget surface (canvas, custom blocks, programmable rendering). Bubbletea-ruby is a binding to a different rendering model with a narrower extension story. We don't want to bet against a feature we already know we want to build.
|
|
28
|
+
|
|
29
|
+
**ADR-0008's original technical reasoning still stands.**
|
|
30
|
+
|
|
31
|
+
- `Command.http`, `Command.system`, `Command.wait`, `Command.cancel` map exactly to the project's verbs (HTTP to GitHub, shelling out to `git`, polling the device-flow endpoint). Bubbletea-ruby followed the Go idiom of "a Command is a closure that returns a message" and required us to write our own thin helpers for these.
|
|
32
|
+
- Rooibos shipped pure-function `Update` testing, snapshot helpers, and a headless terminal style-assertion harness at 0.7. ADR-0010 explicitly flagged bubbletea-ruby's testing story as unverified and a risk to mitigate post-decision.
|
|
33
|
+
- Rooibos's Router DSL maps to the four-fragment design directly. Bubbletea-ruby required us to build the routing.
|
|
34
|
+
|
|
35
|
+
**Same maintainer as `ratatui_ruby`.** Reduces the chance of cross-library impedance mismatch — the same property ADR-0008 cited.
|
|
36
|
+
|
|
37
|
+
## Tradeoffs accepted
|
|
38
|
+
|
|
39
|
+
- **Pre-1.0 framework risk** (same as ADR-0008). Rooibos at 0.7 has "APIs may change before 1.0." Mitigation: pin to a known-good version, bump deliberately, ADR if a bump requires meaningful changes.
|
|
40
|
+
- **Rust toolchain on source-build platforms.** `ratatui_ruby` is precompiled for the common platforms but builds from source on Linux musl, Linux arm64, and macOS x86_64. Document in `MAINTAINER.md` and the README.
|
|
41
|
+
- **Lambda-as-constant style** stays, but is now a maintainer/contributor concern rather than a workshop-attendee concern. Acceptable.
|
|
42
|
+
|
|
43
|
+
## Alternatives considered
|
|
44
|
+
|
|
45
|
+
- **Stay on bubbletea-ruby (ADR-0010).** Rejected: the workshop justification has expired, the Command primitives don't match the project's verbs without writing our own helpers, and the world-map view is a non-trivial extension on top of the bubbletea rendering model.
|
|
46
|
+
- **Wait for Rooibos 1.0.** Same logic ADR-0008 used to reject this: 1.0 timeline unknown, the architectural fit is too good to defer, pinning is sufficient mitigation.
|
|
47
|
+
|
|
48
|
+
## Consequences
|
|
49
|
+
|
|
50
|
+
**On dependencies:** add `rooibos` (`~> 0.7.0`) and `ratatui_ruby` to the gemspec. Remove `bubbletea` and `lipgloss` (which were never actually added — ADR-0010 was a paper decision, no code shipped against it).
|
|
51
|
+
|
|
52
|
+
**On `docs/design.md`:** Already framed in Rooibos terms (the bubbletea revision in ADR-0010 was never propagated through). Minor cleanup needed where doc text mentions bubbletea.
|
|
53
|
+
|
|
54
|
+
**On `docs/design-interface-layer.md`:** The TUI pipeline section refers to "bubbletea Command" and "bubbletea-ruby" — replace with Rooibos terminology. The Result-pattern-matching contract is unchanged (per ADR-0012, which is unaffected).
|
|
55
|
+
|
|
56
|
+
**On `CLAUDE.md`:** Already says Rooibos. No change needed.
|
|
57
|
+
|
|
58
|
+
**On the workshop docs:** Workshop is over; archive `docs/workshop.md` and any workshop-specific framing.
|
|
59
|
+
|
|
60
|
+
**On ADR-0010:** marked Superseded by ADR-0013.
|
|
61
|
+
|
|
62
|
+
**On ADR-0008:** Already superseded by ADR-0010; that link stands. The fact that ADR-0013 restores ADR-0008's *substance* is captured in this ADR's header rather than reopening the older one.
|
|
63
|
+
|
|
64
|
+
## What this *doesn't* change
|
|
65
|
+
|
|
66
|
+
- ADR-0001 (just-in-time auth). Same MVU-shaped flow.
|
|
67
|
+
- ADR-0002, ADR-0003, ADR-0004, ADR-0005, ADR-0007, ADR-0009 (data layer / display rules). Outside the TUI.
|
|
68
|
+
- ADR-0006 (standalone gem). Packaging concern; orthogonal.
|
|
69
|
+
- ADR-0011 (HostAdapter owns host verbs). Service layer; orthogonal.
|
|
70
|
+
- ADR-0012 (output-free service objects, dry-monads). The contract is framework-independent — Rooibos has the same "Commands return messages, not stdout" property bubbletea did, so the substance carries over unchanged.
|
|
71
|
+
- ADR-0014 (Bundler + RubyGems plugins as v1 interfaces). Orthogonal — plugins are CLI-only.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# ADR 0014: Ship Bundler and RubyGems plugins as v1 interfaces
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-05-03
|
|
5
|
+
**Amends:** [ADR-0006](0006-standalone-gem-not-plugin.md), [ADR-0012](0012-output-free-service-objects-three-interface-architecture.md)
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
[ADR-0006](0006-standalone-gem-not-plugin.md) (2026-04-27) decided to ship as a standalone gem rather than a Bundler plugin. The primary reason was workshop scope — Bundler plugin authoring would distract attendees from the actual learning objectives (Ratatui, OAuth, GitHub's API). It explicitly left the door open: *"A future ADR can revisit this if the tool sees real adoption and the plugin UX becomes the bottleneck."*
|
|
10
|
+
|
|
11
|
+
[ADR-0012](0012-output-free-service-objects-three-interface-architecture.md) (2026-05-03 morning) added a RubyGems plugin (`gem contribute`) as a third interface, alongside the standalone CLI and the (then-bubbletea) TUI. It noted that the Bundler plugin decision in ADR-0006 was unchanged.
|
|
12
|
+
|
|
13
|
+
The Blue Ridge Ruby workshop concluded 2026-05-02. ADR-0006's workshop-scope concern is no longer load-bearing.
|
|
14
|
+
|
|
15
|
+
Separately: the v1 release goal now includes both `bundle contribute` *and* `gem contribute` as discoverable entry points, alongside `gem-contribute` itself. This makes the tool reachable from whatever invocation surface a user is already in.
|
|
16
|
+
|
|
17
|
+
## Decision
|
|
18
|
+
|
|
19
|
+
Ship three entry points for v1:
|
|
20
|
+
|
|
21
|
+
1. **`gem-contribute`** — standalone CLI binary. Bare invocation (no subcommand) launches the Rooibos TUI. Subcommands run as CLI verbs.
|
|
22
|
+
2. **`bundle contribute`** — Bundler plugin. CLI-only. Bare invocation runs a default summary verb (TBD: `scan` vs `list all`). Subcommands run as CLI verbs.
|
|
23
|
+
3. **`gem contribute`** — RubyGems plugin. CLI-only. Same shape as `bundle contribute`.
|
|
24
|
+
|
|
25
|
+
All three entry points ship in **a single gem** (`gem-contribute`). One `gem install gem-contribute` registers the standalone binary, the Bundler plugin, and the RubyGems plugin.
|
|
26
|
+
|
|
27
|
+
## Reasoning
|
|
28
|
+
|
|
29
|
+
**The workshop scope-creep argument has expired.** ADR-0006's central rejection was "plugin authoring would distract workshop attendees." Workshop is done; the v1 audience is end users and contributors, not workshop attendees. The remaining ADR-0006 reasoning (UX nicety of `bundle X`) actually *supports* shipping plugins.
|
|
30
|
+
|
|
31
|
+
**Plugin entry points are CLI-only, by design.** Bundler and RubyGems plugin ecosystems are built around CLI subcommands, not interactive TUIs. Users running `bundle contribute` expect the same kind of behavior as `bundle exec`, `bundle install`, etc. — text in, text out. The TUI is a property of the standalone binary; the plugins delegate into the same service-layer entry points the CLI uses.
|
|
32
|
+
|
|
33
|
+
This has a useful architectural consequence: the plugin entry points never need to load Rooibos or `ratatui_ruby`. Plugin install stays lightweight, and plugin invocations don't pay the TUI startup cost.
|
|
34
|
+
|
|
35
|
+
**One gem rather than three.** ADR-0012 sketched a future `rubygems-contribute` gem. Three gems would follow Ruby ecosystem convention (`bundler-X`, `rubygems-X`) but tripples release ceremony, version coordination, and CHANGELOG maintenance. For a project this size, that cost is not earned. One gem with three entry points: one `gem install`, one CHANGELOG, one version, all three interfaces work.
|
|
36
|
+
|
|
37
|
+
**ADR-0012's three-interface framing carries over unchanged.** The service layer (output-free, returns `Result`) is what enables three interfaces to share code. ADR-0014 doesn't add a fourth interface; it confirms the third (Bundler plugin) and locks the packaging to a single gem.
|
|
38
|
+
|
|
39
|
+
## Alternatives considered
|
|
40
|
+
|
|
41
|
+
- **Stay standalone-only (ADR-0006 unmodified).** Rejected: the original justification (workshop scope) no longer applies, and one of v1's product goals is to make the tool reachable from `bundle X` and `gem X` invocations.
|
|
42
|
+
- **Three separate gems.** Rejected: triples release ceremony for marginal architectural cleanliness. The plugin shims would be tiny — a Bundler `Plugin::API` registration and a `Gem::Command` subclass — not worth their own gemspec, version, and changelog.
|
|
43
|
+
- **Ship `bundle contribute` only, defer `gem contribute` to v1.x.** Rejected as worse-of-both-worlds: same release work, half the surface area covered. They're symmetric pieces of work.
|
|
44
|
+
- **TUI in plugins too.** Rejected: not idiomatic for the Bundler/RubyGems plugin ecosystems, doubles the per-invocation startup cost, and the standalone binary already serves the "I want the TUI" use case.
|
|
45
|
+
|
|
46
|
+
## Consequences
|
|
47
|
+
|
|
48
|
+
**On the gemspec:**
|
|
49
|
+
- Add `plugins.rb` (Bundler plugin entry point) per Bundler plugin convention.
|
|
50
|
+
- Add `rubygems_plugin.rb` (RubyGems plugin entry point) per RubyGems plugin convention.
|
|
51
|
+
- The two entry points register their respective subcommand classes (or delegate to a shared dispatch table).
|
|
52
|
+
|
|
53
|
+
**On `lib/gem_contribute/cli.rb`:**
|
|
54
|
+
- The dispatch table becomes the single source of truth for verb registration.
|
|
55
|
+
- Bare-arg behavior diverges per entry point: `gem-contribute` launches TUI; `bundle contribute` and `gem contribute` run a default CLI verb.
|
|
56
|
+
- Consider `dry-cli` to formalize multi-entry-point command registration (deferred decision; see OPEN_QUESTIONS Q6 sub-question).
|
|
57
|
+
|
|
58
|
+
**On the Bundler plugin entry point:** must not require Rooibos or `ratatui_ruby` at load time. TUI loading is gated to the standalone-binary entry point only.
|
|
59
|
+
|
|
60
|
+
**On `ADR-0006`:** status updated to note ADR-0014 amends it. The standalone-gem decision stands; the no-Bundler-plugin decision is reversed.
|
|
61
|
+
|
|
62
|
+
**On `ADR-0012`:** status updated to note ADR-0014 amends it. The three-interface architecture is preserved; the planned separate `rubygems-contribute` gem is replaced by a single `gem-contribute` gem with three entry points.
|
|
63
|
+
|
|
64
|
+
**On `docs/design-interface-layer.md`:** "gem plugin pipeline" section needs updating — the plugin shim is internal to the `gem-contribute` gem, not a separate `rubygems-contribute` gem. Same change for the Bundler plugin section (which doesn't yet exist; needs adding).
|
|
65
|
+
|
|
66
|
+
**On testing:** add at least one smoke test per plugin entry point that proves the plugin registers and dispatches a verb without booting the TUI. Implementation tests live in the existing CLI verb specs (verbs aren't aware of which entry point invoked them).
|
|
67
|
+
|
|
68
|
+
**On release:** the `bundle plugin install gem-contribute` and `gem install gem-contribute` paths both need verification before v1 ships. Add to Phase 6 acceptance.
|
|
69
|
+
|
|
70
|
+
## What this *doesn't* change
|
|
71
|
+
|
|
72
|
+
- ADR-0001 through ADR-0005, ADR-0007 (data layer, display rules). Plugin entry points reuse the same service layer.
|
|
73
|
+
- ADR-0009 (top-level namespace). Plugins live under `GemContribute::` like everything else.
|
|
74
|
+
- ADR-0011 (HostAdapter owns host verbs). Service layer; orthogonal.
|
|
75
|
+
- ADR-0013 (Rooibos as TUI framework). The plugins are CLI-only; framework choice doesn't reach them.
|