Roadmap
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.
Decision history (the short version)
- 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.
Whatβs already done
- 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).
In flight
Nothing β Phase 6 polish is landing. See Phase 6 below.
What hasnβt started
- #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)
v1.0 β Standalone CLI
Phase 0 β Reset workshop-era decisions (DONE)
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.
Phase 1 β Service layer (ADR-0012 Phase 1) (DONE)
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
Phase 2 β CLI pipeline (ADR-0012 Phase 2) (DONE)
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
Phase 6 β Polish, release infrastructure, v1.0
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
Sequencing logic for v1.0
- 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.
v1.x β Plugins, multi-host adapters, polish extensions
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.
Bundler plugin (bundle contribute)
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 gembundle contributeproduces the default summarybundle contribute fix sidekiq/123runs the fix verb- Smoke test verifies plugin registration without booting the TUI
Issue: #38 β Bundler plugin: bundle contribute entry point
RubyGems plugin (gem contribute)
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::Commandgem contribute --helplists the same verbs asgem-contribute --helpgem contribute fix sidekiq/123runs the fix verb- Smoke test verifies plugin registration without booting the TUI
Issue: #39 β RubyGems plugin: gem contribute Gem::Command
Multi-host adapters
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.).
Other v1.x candidates
- π± #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)
v2.0 β Rooibos TUI
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
Out of scope (any version)
- 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.
Issue tracking
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.