v1.0 ships the standalone gem-contribute CLI on rubygems.org against github.com. Output-free service layer (per ADR-0012), real release on rubygems with Trusted Publishing, CHANGELOG, CI.
v1.x adds Bundler plugin (bundle contribute), RubyGems plugin (gem contribute), multi-host adapters (GitLab, gem.coop), and other extensions that ride the existing CLI shape. Architecture for these is locked in (per ADR-0014); shipping is sequenced after 1.0 lands real users.
v2.0 ships the Rooibos TUI as the bare-invocation experience for gem-contribute. Major version because bare-invocation behavior changes.
The descope of TUI and plugins from v1.0 is recorded in ADR-0015.
This document is the plan. Decisions still in flight live in OPEN_QUESTIONS.md and get resolved one at a time.
🌱 marks a good first issue — small, self-contained scope, friendly for someone new to the codebase.
- Workshop is over (2026-05-02). Decisions made primarily for workshop scope are reversed.
- TUI framework: Rooibos (per ADR-0013, supersedes ADR-0010). Bubbletea-ruby was a workshop-driven choice; Rooibos enables the world map view (issue #5) and matches the project's verbs better.
- Single gem with three entry points (per ADR-0014, amends ADR-0006 and ADR-0012). Standalone binary + Bundler plugin + RubyGems plugin all live in the
gem-contributegem. Architecture decision. - v1.0 = CLI alone; plugins at v1.x; TUI at v2.0 (per ADR-0015, amends ADR-0014). Sequencing decision; ADR-0014's architecture stands.
- GitHub-only at v1.0. GitLab/Codeberg/gem.coop adapters are v1.x territory. ADR-0011's architecture is the bet that pays off there.
- Service layer is output-free (per ADR-0012). dry-monads
Resultreturns; dry-operation pipelines; nostdout:in operations.
- Data layer.
LockfileParser,Resolver,GitHubAdapter,Auth,TokenStore,Cache,Operations::Fork,Operations::Clone. - CLI verbs.
init,scan,issues,config,auth,fork,fix,submit. - HostAdapter cleanup. ADR-0011 work landed: adapter owns host verbs, Operations layer composes them, CLI verbs compose Operations.
- ADR-0012 service layer (Phase 1). dry-monads
Result, dry-operation pipelines, dry-initializer initializers, output-freeOperations::*. Merged via PR #48 on 2026-05-04. - Basic CI. rubocop + rspec on push/PR landed via PR #21 (closes #7). Plugin-install smoke deferred to v1.x.
- PR template + automated check (PR #53). Tooling, not part of the v1 phases per se.
- ADR-0012 Phase 2 (CLI output pipeline).
Output::Standard/Output::Null,tty-spinner-backed#progress,tty-promptin Init. Merged via PR #51 on 2026-05-04. - Phase 6 polish (all but #9 and the v1.0 tag).
preferred_labelsconfig (#1, PR #60), owned-upstream fix message (#10, PR #59), idempotentfixre-runs (#54, PR #61), workshop docs archived (#45, PR #58), release infrastructure (#40–#44, PR #55), README rewritten (#46, PR #56).
Nothing — Phase 6 polish is landing. See Phase 6 below.
- #9 —
--label LABELflag for one-off overrides (deferred past v1.0) - Plugin smoke tests (deferred to v1.x with the plugins themselves; tracked under #43)
- The v1.0 tag itself
- v1.x work (plugins, multi-host adapters)
- v2.0 work (Rooibos TUI)
Two new ADRs landed:
- ADR-0013 — Rooibos as the TUI framework, superseding ADR-0010.
- ADR-0014 — Bundler + RubyGems plugins ship inside
gem-contribute, single gem.
ADR header sweep done in commit 00f5a4c. Doc sweeps:
- 🌱 #23 — Sweep
docs/design.mdfor residual bubbletea references - 🌱 #24 — Sweep
docs/design-interface-layer.mdfor "bubbletea" → "Rooibos"
A third descope ADR landed later: ADR-0015 — moves plugins to v1.x and TUI to v2.0.
Made every operation output-free and Result-returning. This is what lets the eventual TUI and plugins reuse the same code paths the standalone CLI uses. Merged via PR #48 on 2026-05-04.
Steps:
- Add
dry-monads,dry-operation,dry-initializerto gemspec. - Remove
stdout:fromOperations::ForkandOperations::Clone. - Define
Operations::Clone::Result = Data.define(:path, :reused)(currently returns a bare path). - Convert both operations to return
Success(Result)/Failure(reason). - Convert
Workflow#build_adapterto returnSuccess(adapter)/Failure(:unauthenticated). - Extract
Operations::Branch(from inline branch logic inCLI::Fix) andOperations::Announce(fromCLI::IssueAnnouncer). - Build
Operations::FixPipelineusingdry-operationto compose Fork → Clone → Branch → Announce. - Replace verbose initializers in
CLI::Fork/CLI::Fixwithdry-initializer(kills the rubocop suppressions). - Update CLI verbs to pattern-match on
Result. RetireWorkflow#with_workflow_rescues.
Acceptance:
- No
Operations::*class acceptsstdout:orstderr: - All operations return
Success/Failure -
FixPipelineexists;CLI::Fix#executecalls it instead of wiring steps inline - All existing tests pass (no behaviour change visible to users)
- No new rubocop suppressions
Issues:
- #25 — Adopt dry-rb suite; convert
Operations::Fork/ClonetoResult - #26 —
Workflow#build_adapterreturnsResult; retirewith_workflow_rescues - #27 — Extract
Operations::BranchandOperations::Announce; buildOperations::FixPipeline - #28 — Migrate
CLI::Fork/CLI::Fixinitializers to dry-initializer
Moved CLI verbs to a semantic output abstraction so the look-and-feel can evolve independently of the service layer. Merged via PR #51 on 2026-05-04.
Steps:
- Introduce
Output::Standard(wraps stdout/stderr;#info,#warn,#error,#progress). - Introduce
Output::Null(for tests). - Replace raw
@stdout/@stderrin every CLI verb with@output. - Add
tty-spinnerforOutput::Standard#progress. - Replace
CLI::Init'sstdout.print+@getswithtty-prompt.
Acceptance:
- No raw
putsto@stdout/@stderrinlib/gem_contribute/cli/ - Long operations show a spinner in TTY contexts and a plain line in non-TTY contexts
-
Init's test suite no longer mocksgets - User-visible CLI output unchanged for non-interactive flows; spinners appear in interactive ones
Issues:
- #29 —
Output::StandardandOutput::Null; migrate CLI verbs off raw stdout/stderr - #30 —
tty-spinner-backed#progress - #31 —
CLI::Initusestty-prompt
Everything required to call it 1.0 and not 0.x. Phase number stays at 6 to preserve the existing phase:6 issue labels and historical references; in the post-ADR-0015 ordering it's the third remaining v1.0 phase.
Pre-existing user-facing issues that fold into this phase:
- 🌱 #1 — Add
preferred_labelsconfig so non-canonical good-first-issue labels are caught — PR #60 - 🌱 #9 — Add
--label LABELflag to scan and issues for one-off overrides (related to #1; deferred past v1.0) - 🌱 #10 — Friendlier message when
fixruns against a repo you own — PR #59 - 🌱 #54 — Make
fixre-runs idempotent (don't error when branch already exists) — PR #61
Release infrastructure:
- 🌱 #40 — Add CHANGELOG.md (closed — file exists and is maintained)
- 🌱 #41 — Add CONTRIBUTING.md (closed — file exists)
- #42 — MAINTAINER.md (closed — release-process and OAuth sections done in PR #55; plugin verification deferred to v1.x with the plugins themselves)
- OAuth App: stay on personal-account App for v1.0 (per Q13); migrate when rate limits bite
- #43 — CI workflow: rubocop + rspec on push/PR done (closed — plugin install smoke deferred to v1.x with plugins)
- #44 — Release workflow with Trusted Publishing (OIDC) — PR #55
- 🌱 #45 — Archive workshop docs to
docs/archive/— PR #58 - #46 — README rewrite for v1 audience — PR #56
- Tag
v1.0.0, push to rubygems
- Phase 0 → 1 → 2 → 6 in strict order. Each unblocks the next.
- 1.0 ships when Phase 6 is acceptably complete. Phases 0, 1, and 2 are done; the remaining work is the polish + release set in Phase 6, with PR #55 cutting the first publish (0.3.1) once it merges.
- v1.x and v2.0 work cannot start until 1.0 is on rubygems with at least a small user base.
Each item below is independently shippable as a 1.x point release (1.1, 1.2, …). Sequencing is a runtime call informed by what 1.0 users actually ask for.
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.
Constraints:
- Plugin entry point MUST NOT require Rooibos or
ratatui_ruby(per ADR-0014). TUI loading is gated to the standalone binary. - Bare
bundle contributerunsscan(resolved per OPEN_QUESTIONS Q3a). bundle contribute <verb>mirrorsgem-contribute <verb>.
Acceptance:
-
bundle plugin install gem-contributeworks against the local gem -
bundle contributeproduces the default summary -
bundle contribute fix sidekiq/123runs the fix verb - Smoke test verifies plugin registration without booting the TUI
Issue: #38 — Bundler plugin: bundle contribute entry point
A rubygems_plugin.rb entry point registers a Gem::Command subclass per RubyGems convention. Same dispatch table.
Constraints:
- Same TUI-load gating as the Bundler plugin.
- Bare
gem contributerunsscan(same decision as Q3a above).
Acceptance:
-
gem install gem-contributeregisters theGem::Command -
gem contribute --helplists the same verbs asgem-contribute --help -
gem contribute fix sidekiq/123runs the fix verb - Smoke test verifies plugin registration without booting the TUI
Issue: #39 — RubyGems plugin: gem contribute Gem::Command
ADR-0011's HostAdapter architecture is the bet that pays off here. Each host is its own adapter implementing the same interface (fork, comment, pull_request_url, etc.).
- 🌱 #3 —
gem-contribute open <gem>to open the repo in the browser - #47 — Meta-PR: use
gem-contributeagainst a real downstream - #49 —
gem-contribute rate <gem|owner/repo>— Good First Repo scoring (needs an ADR before implementation; the scoring rubric is its own design problem)
Umbrella issue: #2 — Implement Rooibos TUI on top of the v0.1 CLI. The major work. Per design.md and ADR-0013. v2.0 because bare-invocation behavior changes (gem-contribute with no args goes from "print USAGE" to "launch TUI"); existing pipe-into-CLI scripts would otherwise break.
Pre-work (Q7 verification):
- Confirm Rooibos's current published version on rubygems.org
- Verify
Command.http,Command.system,Command.wait,Command.cancelstill exist in 0.7.x - Verify Rooibos snapshot test helpers still ship
- Pin
rooibosandratatui_rubyin gemspec
Fragments:
ProjectList— gems from the lockfile with issue counts (lazy-loaded viaCommand.http)IssueList— open issues for the selected project, labels rendered verbatim (ADR-0005)IssueDetail— body, labels, action keys (ffix,copen CONTRIBUTING,oopen in browser)ContributingViewer— rendered markdown (ADR-0007); also surfaces the upstream PR template per #13AuthOverlay— device-flow prompt that fires on:auth_required
(The world map fragment stays post-v2.0 — awaits adoption to make the data interesting. Framework choice locked in now per ADR-0013.)
Wiring:
-
gem-contribute(no args, with aGemfile.lockin cwd) launches the TUI. This is the entry-point change incli.rb. -
gem-contribute(no args, noGemfile.lock) prints a clear "no Gemfile.lock found" message and the USAGE.
Key contracts:
- All async work goes through Rooibos Commands (no
Thread.new, noAsync) Updateis a pure function tested as such (per fragment)- Service-layer calls happen inside Commands and return
Resulttypes (Phase 1's contract) - Command result messages are pattern-matched in
UpdatetoSuccess(...)/Failure(...)shapes
Acceptance:
- All five fragments render and route as designed
-
Updatetest for every fragment, covering each key handler and each command-result branch - At least one snapshot test for the main flow (project list → issue list → issue detail → fix)
- At least one snapshot test for the auth overlay firing mid-flow
-
qquits,Ctrl+Cquits,?shows help overlay - Status bar shows rate limit remaining
Issues (under umbrella #2):
- #32 —
ProjectListfragment with lazy-loaded issue counts - #33 —
IssueListfragment - #34 —
IssueDetailfragment with action keys (f / c / o) - #35 —
ContributingViewerfragment (may absorb #13) - #36 —
AuthOverlayfragment for device-flow prompt - #37 — No-arg
gem-contributelaunches the TUI
- Codeberg/sourcehut adapters — no current ticket, post-v1.x.
- World-map TUI fragment — post-v2.0, awaits adoption. Tracked indirectly via #5's acceptance criteria (which also owns the
KICKED_THE_TIRES.ymldata source). - Private repos /
repoOAuth scope — post-v1, no issue. - PR creation from inside the TUI — design choice, browser-based stays (ADR-0011).
- AI-anything (ADR-0007).
- Label normalization (ADR-0005).
- CONTRIBUTING parsing (ADR-0007).
🌱 #5 itself stays open indefinitely as a sandbox for new contributors to practice the fix → submit loop.
All roadmap work is tracked on the issue tracker. Filter by label:
phase:1,phase:2,phase:6for v1.0 workv1.xfor plugin / multi-host / polish-extension workv2.0for Rooibos TUI work
| Bucket | Issues | Notes |
|---|---|---|
| Phase 0 (DONE) | #23, #24 | Two doc sweeps |
| Phase 1 (DONE) | #25–#28 | Service layer (ADR-0012) |
| Phase 2 (DONE) | #29–#31 | CLI output pipeline |
| Phase 6 (v1.0 polish + release) | #1, #9, #10, #40–#46, #54 | Release infra + papercut polish |
| v1.x | #3, #8, #38, #39, #47, #49, #50 | Plugins, multi-host, extensions |
| v2.0 | #2 (umbrella), #13, #32–#37 | Rooibos TUI |
Out-of-scope items don't get version labels. #5 (sandbox) stays without phase or version labels.