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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +14 -8
  3. data/.github/workflows/ci.yml +26 -0
  4. data/.github/workflows/pr-template-check.yml +100 -0
  5. data/.github/workflows/release.yml +71 -0
  6. data/CHANGELOG.md +38 -0
  7. data/CLAUDE.md +1 -1
  8. data/CONTRIBUTING.md +10 -4
  9. data/MAINTAINER.md +119 -2
  10. data/README.md +13 -1
  11. data/docs/OPEN_QUESTIONS.md +167 -0
  12. data/docs/ROADMAP.md +266 -0
  13. data/docs/adr/0006-standalone-gem-not-plugin.md +1 -1
  14. data/docs/adr/0008-rooibos-tui-framework.md +3 -3
  15. data/docs/adr/0010-charm-ruby-tui-framework.md +2 -2
  16. data/docs/adr/0011-host-adapter-owns-host-verbs.md +58 -0
  17. data/docs/adr/0012-output-free-service-objects-three-interface-architecture.md +79 -0
  18. data/docs/adr/0013-revert-to-rooibos.md +71 -0
  19. data/docs/adr/0014-ship-bundler-and-rubygems-plugins.md +75 -0
  20. data/docs/adr/README.md +7 -3
  21. data/docs/design-interface-layer.md +295 -0
  22. data/docs/design.md +31 -8
  23. data/docs/index.md +1 -1
  24. data/docs/prep-plan.md +6 -6
  25. data/lib/gem_contribute/cli/auth.rb +22 -44
  26. data/lib/gem_contribute/cli/config.rb +29 -15
  27. data/lib/gem_contribute/cli/fix.rb +122 -0
  28. data/lib/gem_contribute/cli/fork.rb +145 -0
  29. data/lib/gem_contribute/cli/init.rb +19 -24
  30. data/lib/gem_contribute/cli/issue_announcer.rb +42 -0
  31. data/lib/gem_contribute/cli/issues.rb +36 -47
  32. data/lib/gem_contribute/cli/platform_tools.rb +33 -0
  33. data/lib/gem_contribute/cli/post_clone_hooks.rb +50 -0
  34. data/lib/gem_contribute/cli/rate_limit_footer.rb +5 -3
  35. data/lib/gem_contribute/cli/scan.rb +20 -16
  36. data/lib/gem_contribute/cli/submit.rb +60 -64
  37. data/lib/gem_contribute/cli/workflow.rb +63 -0
  38. data/lib/gem_contribute/cli.rb +9 -16
  39. data/lib/gem_contribute/config.rb +27 -1
  40. data/lib/gem_contribute/git.rb +49 -0
  41. data/lib/gem_contribute/host_adapter.rb +52 -5
  42. data/lib/gem_contribute/host_adapters/github_adapter.rb +126 -37
  43. data/lib/gem_contribute/operations/announce.rb +52 -0
  44. data/lib/gem_contribute/operations/branch.rb +35 -0
  45. data/lib/gem_contribute/operations/clone.rb +41 -0
  46. data/lib/gem_contribute/operations/fix_pipeline.rb +70 -0
  47. data/lib/gem_contribute/operations/fork.rb +35 -0
  48. data/lib/gem_contribute/output/null.rb +20 -0
  49. data/lib/gem_contribute/output/standard.rb +71 -0
  50. data/lib/gem_contribute/version.rb +1 -1
  51. data/lib/gem_contribute.rb +10 -18
  52. metadata +115 -5
  53. data/lib/gem_contribute/cli/fork_clone_branch.rb +0 -204
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ba185bef64e34f7c11bce3b56e18dc85f9a937a597db98615977b8b29d0306a
4
- data.tar.gz: f1f2c4b77dca2e77da8eaa188a3a2db6d87d4c62eef471c3e9e8d13faff3ecc5
3
+ metadata.gz: 642744289b8e25b5109868d7ef71fbb33e7067d79d8fb9c6580e8a88b566dea6
4
+ data.tar.gz: d2e5cca3a504bd75de537e9b38ba22a0ff2f2b6915162cb9d37f5d065933cc72
5
5
  SHA512:
6
- metadata.gz: 6a52e54602d58024cc94db5cd353556b0f0d18ea6b07a5647a918a96c748547b692531a53ee1f6a1fd7e7a887b9cf30650edce9568a84a76d993245b68d35661
7
- data.tar.gz: 9c4b75036cb2091312b1405797ebea50fe9ab1103ae793081834c03a5e18a1151c006f127e84f160b234a8ab6b8037e243bf3d09feea962daeb8854f96c3770b
6
+ metadata.gz: a2600e3b0ee2adf44fcadab52639e446aae0d15f7d4843c111d68d86b3fd726bed20d72ad62063c72230656cdf8c62a52d7fc1507b7e5fb85032678f77ae99a7
7
+ data.tar.gz: 1f208fe93824d839c8ca78cf842a5cc0fcbebcf3c9b4f89cf8d9fcdd1c403ce19b9b1f0c1e664b1a3b5d681e45f80559e8c333d3ba752111e64726b633491ed9
@@ -2,21 +2,27 @@
2
2
 
3
3
  <!-- One or two sentences. What changes, and why. The "why" is the part that's hard to recover later. -->
4
4
 
5
- ## Review preference
5
+ ## Linked issue / ADR
6
6
 
7
- Lowering friction is the whole point of this project. Pick whichever fits — defaults to the first if you don't tick anything.
7
+ <!--
8
+ Issue # this closes (or "none — drive-by fix").
9
+ For non-trivial changes: which ADR(s) does this touch? If you skipped that step, write "no ADR" plus a one-line reason.
10
+ -->
8
11
 
9
- - [ ] **Ship it.** Review for correctness, tests, and design fit. Skip style nits unless they're load-bearing.
10
- - [ ] **Full review, please.** Feedback on style, naming, idioms, and alternative designs welcome. I want the deep version, whether to learn the Ruby way or to stress-test a pattern I'm trying.
11
-
12
- ## Working agreement check
12
+ ## Working agreement
13
13
 
14
14
  - [ ] Single concern (multi-concern PRs get bounced — see [CLAUDE.md](../CLAUDE.md))
15
- - [ ] ADRs touched: <!-- list ADR numbers, or "none" -->
16
15
  - [ ] `bin/rubocop` and `bin/rspec` pass locally
17
16
  - [ ] New behavior has a test; bug fix has a regression test
18
17
  - [ ] Async work goes through Rooibos Commands (no direct threads / `Async` / synchronous shellouts)
19
18
 
19
+ ## Test plan
20
+
21
+ <!--
22
+ How was this verified? Spec output, manual smoke, screenshots — whatever applies.
23
+ "`bin/rspec` passes" is the floor, not a test plan.
24
+ -->
25
+
20
26
  ## Notes for reviewer
21
27
 
22
- <!-- Tradeoffs, open questions, follow-ups — anything not obvious from the diff. -->
28
+ <!-- Tradeoffs, open questions, follow-ups — anything not obvious from the diff. Delete the section if there's nothing to add. -->
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: Specs and Rubocop
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: "3.2"
20
+ bundler-cache: true
21
+
22
+ - name: Run specs
23
+ run: bin/rspec
24
+
25
+ - name: Run rubocop
26
+ run: bin/rubocop
@@ -0,0 +1,100 @@
1
+ name: PR template check
2
+
3
+ # Verifies that PR descriptions include all required sections from
4
+ # .github/PULL_REQUEST_TEMPLATE.md. Posts (or updates) a single comment
5
+ # listing what's missing, and fails the check until the body is fixed.
6
+ #
7
+ # Security note: pull_request_target gives us write access to comment
8
+ # back on the PR. We never check out PR HEAD; the body is read via the
9
+ # event payload (data, not code).
10
+
11
+ on:
12
+ pull_request_target:
13
+ types: [opened, edited, reopened, synchronize]
14
+
15
+ permissions:
16
+ pull-requests: write
17
+
18
+ jobs:
19
+ template-check:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Verify required sections
23
+ env:
24
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25
+ PR_NUMBER: ${{ github.event.pull_request.number }}
26
+ REPO: ${{ github.repository }}
27
+ PR_BODY: ${{ github.event.pull_request.body }}
28
+ run: |
29
+ set -euo pipefail
30
+
31
+ MARKER="<!-- pr-template-check -->"
32
+
33
+ # Required headings — must appear verbatim somewhere in the body.
34
+ required=(
35
+ "## Summary"
36
+ "## Linked issue / ADR"
37
+ "## Working agreement"
38
+ "## Test plan"
39
+ )
40
+ # "## Notes for reviewer" is intentionally optional — the template
41
+ # tells contributors to delete it when there's nothing to add.
42
+
43
+ missing=()
44
+ for heading in "${required[@]}"; do
45
+ if ! grep -Fq "$heading" <<< "${PR_BODY:-}"; then
46
+ missing+=("$heading")
47
+ fi
48
+ done
49
+
50
+ # Strip HTML comments and template placeholder text, then check
51
+ # the body has *some* substance. Catches PRs filed with the raw
52
+ # unmodified template.
53
+ stripped=$(printf '%s' "${PR_BODY:-}" | perl -0777 -pe 's/<!--.*?-->//gs')
54
+ # Drop heading lines and checklist scaffolding; what's left should
55
+ # be the contributor's own prose.
56
+ prose=$(printf '%s' "$stripped" | grep -Ev '^\s*(##|- \[[ x]\]|$)' || true)
57
+ prose_chars=$(printf '%s' "$prose" | tr -d '[:space:]' | wc -c | tr -d ' ')
58
+
59
+ # Find any prior bot comment so we can edit instead of stacking.
60
+ prior_id=$(gh api "repos/$REPO/issues/$PR_NUMBER/comments" \
61
+ --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | contains(\"$MARKER\"))) | .id" \
62
+ | head -n1)
63
+
64
+ if [ ${#missing[@]} -eq 0 ] && [ "$prose_chars" -ge 40 ]; then
65
+ echo "PR body looks good."
66
+ if [ -n "$prior_id" ]; then
67
+ gh api -X PATCH "repos/$REPO/issues/comments/$prior_id" \
68
+ -f body="$MARKER
69
+ ✅ PR template check passed. Thanks for filling out the template."
70
+ fi
71
+ exit 0
72
+ fi
73
+
74
+ {
75
+ echo "$MARKER"
76
+ echo "👋 This PR is missing pieces from the [PR template](https://github.com/$REPO/blob/main/.github/PULL_REQUEST_TEMPLATE.md):"
77
+ echo ""
78
+ if [ ${#missing[@]} -gt 0 ]; then
79
+ echo "**Missing sections:**"
80
+ for m in "${missing[@]}"; do
81
+ echo "- \`$m\`"
82
+ done
83
+ echo ""
84
+ fi
85
+ if [ "$prose_chars" -lt 40 ]; then
86
+ echo "**Body looks unfilled** — the template comments are still there but no actual content has been written. Please fill in at least the Summary and Test plan."
87
+ echo ""
88
+ fi
89
+ echo "Edit the PR description (no new commit needed) and the check will re-run automatically. See [CLAUDE.md](https://github.com/$REPO/blob/main/CLAUDE.md) for why these sections matter."
90
+ } > /tmp/comment.md
91
+
92
+ if [ -n "$prior_id" ]; then
93
+ gh api -X PATCH "repos/$REPO/issues/comments/$prior_id" \
94
+ -F body=@/tmp/comment.md
95
+ else
96
+ gh pr comment "$PR_NUMBER" --repo "$REPO" --body-file /tmp/comment.md
97
+ fi
98
+
99
+ echo "::error::PR template is incomplete — see the bot comment on the PR."
100
+ exit 1
@@ -0,0 +1,71 @@
1
+ name: Release
2
+
3
+ # Triggers on `v*` tag pushes (e.g. `v0.4.0`). Verifies the tag matches
4
+ # `GemContribute::VERSION` and that CHANGELOG.md has a dated section for
5
+ # it, then runs rubocop + rspec as a final gate and publishes to
6
+ # rubygems.org via Trusted Publishing (OIDC). No `RUBYGEMS_API_KEY`
7
+ # secret — the rubygems.org-side trusted-publisher entry mints a
8
+ # short-lived token from the GitHub Actions OIDC claim. Compatible with
9
+ # `rubygems_mfa_required = true`, which the gemspec already sets.
10
+ #
11
+ # First-run setup (one-time, before the first tag push): see
12
+ # MAINTAINER.md → "Cutting a release". The rubygems.org Trusted
13
+ # Publisher entry must exist before this workflow can succeed.
14
+ #
15
+ # The `release` environment scopes the OIDC claim — only workflow runs
16
+ # in that environment can authenticate to rubygems.org. Configure it at
17
+ # Settings → Environments in the GitHub repo (no secrets, no protection
18
+ # rules required, but adding a "required reviewer" gives a manual
19
+ # approval gate before publishes).
20
+
21
+ on:
22
+ push:
23
+ tags:
24
+ - 'v*'
25
+
26
+ permissions:
27
+ contents: write # create the GitHub release with auto-generated notes
28
+ id-token: write # request OIDC token for Trusted Publishing
29
+
30
+ jobs:
31
+ release:
32
+ name: Build, test, and publish
33
+ runs-on: ubuntu-latest
34
+ environment: release
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ - name: Set up Ruby
39
+ uses: ruby/setup-ruby@v1
40
+ with:
41
+ ruby-version: "3.2"
42
+ bundler-cache: true
43
+
44
+ - name: Verify tag matches gemspec version and CHANGELOG section
45
+ env:
46
+ REF_NAME: ${{ github.ref_name }}
47
+ run: |
48
+ set -euo pipefail
49
+ tag_version="${REF_NAME#v}"
50
+ gem_version=$(ruby -r./lib/gem_contribute/version -e 'print GemContribute::VERSION')
51
+
52
+ if [ "$tag_version" != "$gem_version" ]; then
53
+ echo "::error::Tag $REF_NAME does not match GemContribute::VERSION ($gem_version)."
54
+ echo "Bump lib/gem_contribute/version.rb or retag, then push."
55
+ exit 1
56
+ fi
57
+
58
+ if ! grep -Fq "## [$gem_version]" CHANGELOG.md; then
59
+ echo "::error::CHANGELOG.md is missing a '## [$gem_version]' section."
60
+ echo "Add a dated section before tagging — see existing entries for the shape."
61
+ exit 1
62
+ fi
63
+
64
+ - name: rubocop
65
+ run: bin/rubocop
66
+
67
+ - name: rspec
68
+ run: bin/rspec
69
+
70
+ - name: Publish to rubygems.org via Trusted Publishing
71
+ uses: rubygems/release-gem@v1
data/CHANGELOG.md CHANGED
@@ -4,6 +4,44 @@ All notable changes to this project will be documented here. The format is based
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.3.1] - 2026-05-04
8
+
9
+ ### Added
10
+
11
+ - Release workflow (`.github/workflows/release.yml`) — `v*` tag push triggers a publish to rubygems.org via [Trusted Publishing](https://guides.rubygems.org/trusted-publishing/) (OIDC). No `RUBYGEMS_API_KEY` secret involved; rubygems.org issues a short-lived token from the GitHub Actions OIDC claim. The workflow verifies the tag matches `GemContribute::VERSION` and that `CHANGELOG.md` has a dated section for it before running rubocop, rspec, and the publish step. First-time setup (rubygems.org pending-trusted-publisher entry, `release` GitHub Environment) is documented in `MAINTAINER.md` (closes [#44](https://github.com/cdhagmann/gem-contribute/issues/44)).
12
+
13
+ ### Fixed
14
+
15
+ - `Gemfile.lock` regenerated to match `GemContribute::VERSION` after the 0.3.0 bump (commit `077eadb`) updated `version.rb` without running `bundle install`. CI runs bundler in deployment mode and was failing on the lockfile/gemspec mismatch. The MAINTAINER.md per-release checklist now calls out `bundle install` as an explicit step so the next bump doesn't repeat this.
16
+
17
+ ## [0.3.0] - 2026-05-04
18
+
19
+ ### Added
20
+
21
+ - `gem-contribute fork <gem>` — the look-around-first counterpart to `fix`: fork the gem's repo, clone it, leave you on the default branch with no issue-tied work yet. Same `-e` / `-a` flags. Use this when you want to read the code before deciding whether to commit to a specific issue (closes [#12](https://github.com/cdhagmann/gem-contribute/issues/12)).
22
+ - `gem-contribute fix -e` opens your editor in the clone directory after fork/clone/branch. Uses the new `editor` config key, falling back to `$EDITOR` (closes [#14](https://github.com/cdhagmann/gem-contribute/issues/14)).
23
+ - `gem-contribute fix -a` launches your configured AI coding tool (new `ai_tool` config key) with the clone directory as cwd. Combine with `-e` to open both — editor first, AI tool second (closes [#14](https://github.com/cdhagmann/gem-contribute/issues/14)).
24
+ - `gem-contribute fix` posts a "👋 I've started working on this" comment to the issue by default so other contributors don't double up on the same work. Opt out per-invocation with `--no-comment`, globally with `comment_on_fix: false` in config, or per-repo via `comment_on_fix_overrides` (YAML-only). Posting is soft-fail — the fork/clone/branch part still succeeds even if the comment can't be posted (closes [#18](https://github.com/cdhagmann/gem-contribute/issues/18)).
25
+ - `scan` appends `· N claimed` to project lines whose open issues have already been claimed via the working-on-this marker, so you can spot already-in-progress work at a glance (closes [#20](https://github.com/cdhagmann/gem-contribute/issues/20)).
26
+ - `issues <gem|all>` prefixes claimed issues with `[claimed]` for the same reason (closes [#20](https://github.com/cdhagmann/gem-contribute/issues/20)).
27
+ - Long-running CLI operations (`fix`'s fork → clone → branch pipeline; `submit`'s `git push`) now show a tty-spinner in interactive terminals. Non-TTY contexts (CI, piped output, redirected stderr) fall back to plain status lines — no behavior change for scripted use (closes [#30](https://github.com/cdhagmann/gem-contribute/issues/30)).
28
+
29
+ ### Changed
30
+
31
+ - `gem-contribute init` reads input through `tty-prompt`. Cosmetic side effect: default values are now displayed in parens — `(~/code/oss)` — instead of brackets — `[~/code/oss]` — matching tty-prompt's convention. Y/n parsing is now built-in instead of hand-rolled (closes [#31](https://github.com/cdhagmann/gem-contribute/issues/31)).
32
+ - Internal class `ForkCloneBranch` renamed to `Fix` (the long name was cumbersome and didn't mirror the `fix` CLI verb). User-facing CLI surface unchanged.
33
+ - Internal architecture: `HostAdapter` now owns every host-API verb (`fork`, `comment`, `pull_request_url`) plus host-specific URL templating (`clone_url`, `repo_url`); the new `Operations::Fork` / `Operations::Clone` primitives compose those with `Git`; `CLI::Fork` and `CLI::Fix` are thin compositions on top. The fork-readiness polling moved into the adapter — multi-host adapters can model readiness however the host actually works. See [ADR-0011](docs/adr/0011-host-adapter-owns-host-verbs.md). User-facing CLI surface unchanged.
34
+ - Internal architecture (ADR-0012 Phase 1): `Operations::*` classes are now output-free and return `dry-monads` `Success` / `Failure` `Result` types; new `Operations::Branch` and `Operations::Announce` primitives; `Operations::FixPipeline` composes Fork → Clone → Branch → Announce via `dry-operation`; `Workflow#build_adapter` returns a `Result` and the old `with_workflow_rescues` rescue chain is gone; `CLI::Fork` / `CLI::Fix` initializers use `dry-initializer`. See [ADR-0012](docs/adr/0012-output-free-service-objects-three-interface-architecture.md). User-facing CLI surface unchanged.
35
+ - Internal architecture (ADR-0012 Phase 2): every CLI verb writes through a semantic `Output::Standard` (or `Output::Null` in tests) abstraction — `#info`, `#progress`, `#warn`, `#error` — instead of raw `@stdout.puts` / `@stderr.puts`. `#progress` accepts an optional block and wraps a `tty-spinner` in TTY contexts. Existing constructors still accept `stdout:` / `stderr:` and auto-wrap them, so test suites injecting StringIO streams keep working unchanged. New deps: `tty-spinner ~> 0.9`, `tty-prompt ~> 0.23` (both pure-Ruby) (closes [#29](https://github.com/cdhagmann/gem-contribute/issues/29)).
36
+
37
+ ### Removed
38
+
39
+ - The `fork-clone-branch` CLI alias has been removed. Use `gem-contribute fix` instead — same behavior, shorter to type.
40
+
41
+ ### Fixed
42
+
43
+ - `gem-contribute fork` no longer prints `cd <path> && explore` — `explore` was meant as English but read as a (non-existent) shell command, so a copy-paste produced `command not found`. The "Next:" hint is now conditional on `-e` / `-a`: with neither flag it suggests `cd <path> && $EDITOR .`; with either flag it skips the directory step (you're already in your editor) and just points at the `fix` command.
44
+
7
45
  ## [0.2.0] - 2026-05-02
8
46
 
9
47
  ### Added
data/CLAUDE.md CHANGED
@@ -4,7 +4,7 @@ This file is read by Claude Code when working in this repository. Treat it as th
4
4
 
5
5
  ## Project shape
6
6
 
7
- `gem-contribute` is a terminal UI that reads a project's `Gemfile.lock`, surfaces open contributable issues from the gems' source repositories, and offers one-keystroke fork-clone-branch.
7
+ `gem-contribute` is a terminal UI that reads a project's `Gemfile.lock`, surfaces open contributable issues from the gems' source repositories, and offers a one-keystroke `fix` flow.
8
8
 
9
9
  Read `docs/design.md` and the ADRs in `docs/adr/` before making non-trivial changes. The design doc describes the architecture and the ADRs explain why specific decisions were made. If a change conflicts with an ADR, propose updating the ADR first; don't silently violate it.
10
10
 
data/CONTRIBUTING.md CHANGED
@@ -6,11 +6,17 @@ Thanks for considering a contribution. This project is *about* lowering the fric
6
6
 
7
7
  ```
8
8
  gem install gem-contribute
9
- gem-contribute init # set clone_root and auth with GitHub
10
- gem-contribute fix gem-contribute/issue-5
9
+ gem-contribute init # set clone_root and auth with GitHub
10
+ gem-contribute fork gem-contribute # fork + clone; lands on the default branch
11
11
  bundle install
12
- bin/rspec # tests should pass on a clean checkout
13
- bin/gem-contribute # tool should run against this repo's own Gemfile.lock
12
+ bin/rspec # tests should pass on a clean checkout
13
+ bin/gem-contribute # tool should run against this repo's own Gemfile.lock
14
+ ```
15
+
16
+ Once you've picked an issue to work on, branch off the default with `fix`:
17
+
18
+ ```
19
+ gem-contribute fix gem-contribute/<issue#> # creates gem-contribute/issue-<N> off the default branch
14
20
  ```
15
21
 
16
22
  ## What we welcome
data/MAINTAINER.md CHANGED
@@ -86,7 +86,124 @@ older gem versions continue to work.
86
86
 
87
87
  ## Cutting a release
88
88
 
89
- (Stub fill in when we cut v0.1.0 to RubyGems. Notes will live here:
90
- gemspec metadata checks, `bundle exec rake release` flow, signing, etc.)
89
+ Releases publish to rubygems.org via [Trusted Publishing][gh-trusted-pub]
90
+ (OIDC) there is no `RUBYGEMS_API_KEY` secret and no manual `gem push`.
91
+ A `v*` tag push triggers `.github/workflows/release.yml`, which verifies
92
+ the tag matches `GemContribute::VERSION`, checks that `CHANGELOG.md` has a
93
+ dated section for the version, runs rubocop and rspec, and then publishes.
94
+
95
+ ### One-time setup (before the first automated release)
96
+
97
+ The rubygems.org Trusted Publisher entry must exist before any tag push
98
+ can succeed. For an unclaimed gem name, use the **pending publisher**
99
+ flow:
100
+
101
+ 1. Sign in at <https://rubygems.org>.
102
+
103
+ 2. Go to <https://rubygems.org/profile/me/oidc/pending_trusted_publishers/new>.
104
+
105
+ 3. Fill in the form:
106
+
107
+ | Field | Value |
108
+ |-------------------|----------------------|
109
+ | Gem name | `gem-contribute` |
110
+ | Repository owner | `cdhagmann` |
111
+ | Repository name | `gem-contribute` |
112
+ | Workflow filename | `release.yml` |
113
+ | Environment | `release` |
114
+
115
+ The "Environment" value matches `environment: release` in the
116
+ workflow. Leave blank if you removed that line; otherwise both must
117
+ match exactly.
118
+
119
+ 4. Submit. The publisher entry is now "pending" — it has no gem attached
120
+ yet. The first successful publish from this workflow claims the name
121
+ and promotes the entry to a regular trusted publisher.
122
+
123
+ After the gem is published, you can add additional trusted publishers
124
+ (e.g. for a fork or replacement workflow) from the gem's settings page on
125
+ rubygems.org instead of the pending-publisher flow.
126
+
127
+ ### Configure the GitHub environment (one-time)
128
+
129
+ The workflow runs in an `environment: release` job. Create the environment
130
+ in the repo so the OIDC claim carries the correct value:
131
+
132
+ 1. GitHub repo → Settings → Environments → **New environment**.
133
+ 2. Name it `release`.
134
+ 3. (Optional) Add yourself as a **Required reviewer** for a manual
135
+ approval gate before each publish. Recommended for the first few
136
+ releases until you trust the workflow.
137
+ 4. No secrets to configure. Trusted publishing replaces secrets entirely.
138
+
139
+ ### Per-release checklist
140
+
141
+ When cutting a new version:
142
+
143
+ 1. **Bump the version.** Edit `lib/gem_contribute/version.rb` to the new
144
+ `MAJOR.MINOR.PATCH`. Follow [SemVer](https://semver.org/).
145
+
146
+ 2. **Regenerate `Gemfile.lock`.** Run `bundle install`. The lockfile's
147
+ `gem-contribute (X.Y.Z)` line must match the new version in both the
148
+ PATH spec at the top and the CHECKSUMS section near the bottom. CI
149
+ runs bundler in deployment/`--frozen` mode and refuses to install if
150
+ the lockfile is out of sync with the gemspec.
151
+
152
+ 3. **Update CHANGELOG.md.** Move the contents of `[Unreleased]` into a
153
+ new dated section: `## [X.Y.Z] - YYYY-MM-DD`. Leave `[Unreleased]`
154
+ empty for the next cycle. The release workflow refuses to publish if
155
+ it can't find a `## [X.Y.Z]` section matching the tag.
156
+
157
+ 4. **Commit on `main`.** Bump version.rb, the regenerated Gemfile.lock,
158
+ and CHANGELOG.md all in the same commit. Conventional message:
159
+ `Bump gem-contribute to X.Y.Z`.
160
+
161
+ 5. **Tag and push.**
162
+
163
+ ```sh
164
+ git tag -a vX.Y.Z -m "X.Y.Z"
165
+ git push origin main vX.Y.Z
166
+ ```
167
+
168
+ 6. **Watch the Actions tab.** The workflow will:
169
+ - verify the tag/version/CHANGELOG match
170
+ - run rubocop and rspec
171
+ - request an OIDC token, exchange it for a short-lived rubygems API
172
+ key, and publish the gem
173
+ - create a draft GitHub release with auto-generated notes
174
+
175
+ If the environment has a required-reviewer protection rule, the
176
+ workflow will pause for your manual approval before the publish step.
177
+
178
+ 7. **Sanity check.** After publish, `gem info gem-contribute` should show
179
+ the new version. The draft GitHub release is yours to edit and publish
180
+ when ready.
181
+
182
+ ### Troubleshooting
183
+
184
+ - **"Tag … does not match GemContribute::VERSION"** — version.rb is out of
185
+ sync with the tag. Either delete the tag and bump version.rb, or
186
+ re-tag at the right SHA after fixing version.rb.
187
+ - **"CHANGELOG.md is missing a section for …"** — add the dated section,
188
+ amend the bump commit, force-push, delete the old tag, retag, push.
189
+ (Force-push is fine on the bump commit before the publish has
190
+ succeeded.)
191
+ - **OIDC failure / "no trusted publisher matches"** — check the
192
+ rubygems.org publisher entry: gem name, repo owner, repo name,
193
+ workflow filename (`release.yml`, not the full path), and environment
194
+ must all match. The workflow filename is the basename only, no
195
+ `.github/workflows/` prefix.
196
+ - **`gem push` fails with `multifactor authentication required`** —
197
+ trusted publishing satisfies MFA. If you see this error, the workflow
198
+ fell back to a non-OIDC path; verify `permissions.id-token: write` is
199
+ set on the job.
200
+
201
+ ### Yanking a release
202
+
203
+ Yanks happen via the rubygems.org web UI or `gem yank gem-contribute -v
204
+ X.Y.Z`. There is no automated yank flow — by design. If you need to yank,
205
+ also delete the corresponding `vX.Y.Z` tag and GitHub release so the
206
+ record on GitHub matches what's available on rubygems.org.
91
207
 
92
208
  [gh-device-flow]: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
209
+ [gh-trusted-pub]: https://guides.rubygems.org/trusted-publishing/
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # gem-contribute
2
2
 
3
+ [![CI](https://github.com/cdhagmann/gem-contribute/actions/workflows/ci.yml/badge.svg)](https://github.com/cdhagmann/gem-contribute/actions/workflows/ci.yml)
4
+
3
5
  Find contributable issues in the gems your project already depends on.
4
6
 
5
7
  ```
@@ -39,7 +41,8 @@ gem-contribute init One-time interactive setup (sets clone_roo
39
41
  gem-contribute scan [path] Summarize the contributable surface of a Gemfile.lock.
40
42
  gem-contribute issues <gem|all> List "good first issue" issues for one gem (or all).
41
43
  gem-contribute auth login Authenticate with GitHub via OAuth device flow.
42
- gem-contribute fix <gem>/<issue#> Fork the gem's repo, clone the fork, branch from main.
44
+ gem-contribute fork <gem|owner/repo> Fork and clone any GitHub repo, land on the default branch.
45
+ gem-contribute fix <gem>/<issue#> Fork, clone, and branch from main for a specific issue.
43
46
  gem-contribute submit Push the branch and open a pre-filled PR in the browser.
44
47
  gem-contribute config set <key> <val> Persist user preferences (e.g. clone_root).
45
48
  ```
@@ -56,6 +59,15 @@ $ cd ~/code/oss/rubocop/rubocop # whatever clone_root you set during in
56
59
  $ gem-contribute submit # push + open the PR compare page in your browser
57
60
  ```
58
61
 
62
+ Or, when you want to look around a project before picking an issue, use `fork`:
63
+
64
+ ```sh
65
+ $ gem-contribute fork rubocop # fork-and-clone a gem by name
66
+ $ gem-contribute fork rubyevents/rubyevents -e # fork-and-clone any GitHub repo and open your editor
67
+ ```
68
+
69
+ `fork` does the same fork-clone-upstream sequence as `fix` but stops on the default branch — no issue branch, no comment. Handy for "build it locally and decide what to fix later." When you've picked an issue, `gem-contribute fix <gem>/<issue#>` branches off cleanly.
70
+
59
71
  The auth step (run automatically by `init`, or directly via `gem-contribute auth login`) opens GitHub's device-flow page in your browser and copies the one-time code to your clipboard — same UX as `gh auth login`, no token paste, no client secret. Tokens cache at `~/.config/gem-contribute/auth.json` (mode 0600).
60
72
 
61
73
  ## Configuration
@@ -0,0 +1,167 @@
1
+ # Open Questions — Roadmap to v1
2
+
3
+ Working list of unresolved planning questions for the v1 roadmap (TUI + CLI + `bundle contribute` + `gem contribute`). Walked through one at a time with the maintainer; each gets crossed off and folded into the ROADMAP / ADRs as it's answered.
4
+
5
+ Order is rough priority — top items block downstream design, bottom items are polish.
6
+
7
+ ---
8
+
9
+ ## Q1. ~~What is the "world map view"?~~ — ANSWERED 2026-05-03
10
+
11
+ A near-easter-egg view showing the locations of people who've kicked the tires on `gem-contribute`. Tracked as [issue #5](https://github.com/cdhagmann/gem-contribute/issues/5). Not a primary fragment — a hidden/discoverable view.
12
+
13
+ **Implications this surfaces:**
14
+ - Requires some form of opt-in usage reporting (the tool has to phone home with *something* to populate the map). New component: a tire-kicker reporting backend.
15
+ - Privacy/consent UX needs to live somewhere — likely first-run prompt or `init` flow.
16
+ - Backend service is itself a v1 dependency *if* the map ships in v1.
17
+
18
+ → See **Q1a** below.
19
+
20
+ ---
21
+
22
+ ## Q1a. ~~Does the world map ship in v1?~~ — ANSWERED 2026-05-03
23
+
24
+ No. Preserving the option by choosing Rooibos now; tire-kicker map waits until there's enough adoption to make the data interesting. No backend work in v1 scope.
25
+
26
+ **ADR-0013 reasoning lock-in:**
27
+ 1. Workshop onboarding cost — the main argument for bubbletea — is no longer a constraint (workshop ended 2026-05-02)
28
+ 2. Preserving the world map view (issue #5) as a future option
29
+ 3. ADR-0008's original technical reasoning still stands (Command primitives matched our verbs, snapshot test helpers, fractal Router architecture)
30
+ 4. Bubbletea-ruby's testing story was unverified per ADR-0010; Rooibos shipped tested helpers
31
+
32
+ ---
33
+
34
+ ## Q2. ~~Packaging shape~~ — ANSWERED 2026-05-03
35
+
36
+ One gem. `gem-contribute` ships the standalone CLI, the Bundler plugin hooks, and the RubyGems plugin hooks all together. ADR-0012's mention of a future `rubygems-contribute` gem is overridden by ADR-0014 (the new plugin ADR).
37
+
38
+ ---
39
+
40
+ ## Q3. ~~What do `bundle contribute` / `gem contribute` do?~~ — ANSWERED 2026-05-03
41
+
42
+ CLI only — no TUI from the plugins. Reasoning: Bundler and RubyGems plugins aren't typically interactive; they're CLI subcommands. The TUI is a property of the standalone `gem-contribute` binary.
43
+
44
+ - `gem-contribute` (no args) → TUI
45
+ - `bundle contribute` (no args) → CLI default (`scan` or `list all` — see **Q3a**)
46
+ - `gem contribute` (no args) → CLI default (same)
47
+ - `<any>` `<verb>` → CLI verb
48
+
49
+ **Architectural consequence:** the plugin entry points never need to load Rooibos. Keeps plugin install lightweight.
50
+
51
+ ---
52
+
53
+ ## Q3a. Bare-call default for the plugins: `scan` or `list all`?
54
+
55
+ User flagged as undecided. Both are read-only summary actions; difference is granularity and verbosity.
56
+
57
+ - **`scan`** — current verb; prints the summary table (`47 gems · 44 on github.com · 2 on gitlab.com · …`) plus top contributable projects by issue count. Hits the network for the issue counts (cached).
58
+ - **`list all`** — would presumably print every gem with its resolved source. Less network, more data, less curated.
59
+
60
+ No urgency to pick — can be deferred to Phase 4. Worth deciding alongside the verb taxonomy review (does `list` exist as a separate verb? sub-verbs of `list`?).
61
+
62
+ ---
63
+
64
+ ## Q4. ~~Default behavior of bare `gem-contribute`~~ — ANSWERED 2026-05-03
65
+
66
+ Packwerk-style launcher: `gem-contribute` (no args) opens a mini TUI that lists subcommands; arrow keys + enter pick one. `gem-contribute <verb>` runs the verb directly with no TUI in the way.
67
+
68
+ → See **Q4a**: this reframes what the "full TUI" is.
69
+
70
+ ---
71
+
72
+ ## Q4a. ~~What happens to the design.md "full TUI"?~~ — ANSWERED 2026-05-03
73
+
74
+ Bare `gem-contribute` launches the design.md four-fragment TUI (ProjectList → IssueList → IssueDetail → ContributingViewer + AuthOverlay + the eventual WorldMap). The packwerk reference was establishing "yes, raw command launches a TUI" as precedent — not a directive to mirror packwerk's exact subcommand-picker shape. Subcommands stay CLI.
75
+
76
+ Phase 3 of ROADMAP doesn't need rewriting; just confirm the entry-point wiring in `cli.rb` (no-arg → launch TUI vs print USAGE).
77
+
78
+ ---
79
+
80
+ ## Q5. ~~Multi-host adapters in v1?~~ — ANSWERED 2026-05-03
81
+
82
+ GitHub-only at v1.0. v1.x point releases will add adapters (GitLab, Codeberg, etc.). ADR-0011's architecture is the bet that paying off.
83
+
84
+ ---
85
+
86
+ ## Q6. ~~Does ADR-0012's dry-rb adoption survive the framework revert?~~ — ANSWERED 2026-05-03
87
+
88
+ Yes. We need both a CLI and a TUI; output-free service objects with `Result` returns are what lets both consume the same service layer. ADR-0013 (Rooibos revert) doesn't touch ADR-0012.
89
+
90
+ Sub-question on `dry-cli` adoption is **deferred to Phase 4/5** when the plugin entry points get wired — easier to decide once the plugin shape is concrete.
91
+
92
+ ---
93
+
94
+ ## Q7. Rooibos 0.7.x verification — ASSIGNED TO ME
95
+
96
+ Pre-Phase-3 task (no user decision needed): confirm Rooibos's current version on rubygems.org, verify Command primitives still exist, verify snapshot test helpers still ship. Folded into ROADMAP Phase 3.
97
+
98
+ ---
99
+
100
+ ## Q8. ~~Workshop docs disposition~~ — MY CALL: archive
101
+
102
+ Moving workshop-era docs (`workshop.md`, `talk/`, `workshop-issues/`, `prep-plan.md`) to `docs/archive/` in Phase 6. Preserves history without polluting the active doc surface. Flag if you'd rather delete or keep them in place.
103
+
104
+ ---
105
+
106
+ ## Q9. ~~Test framework + cassette policy~~ — MY CALL: keep design.md's strategy as-is
107
+
108
+ RSpec, VCR cassettes committed, no formal coverage target (just "every public method, every Update branch"). Snapshot helpers come from Rooibos — verified in Q7. Flag if you want a different testing posture for v1.
109
+
110
+ ---
111
+
112
+ ## Q10. ~~v1 out-of-scope confirmation~~ — MY CALL: existing exclusions stand
113
+
114
+ All ADR-mandated out-of-scope items remain out for v1.0:
115
+ - Private repos / `repo` OAuth scope
116
+ - PR creation from inside the TUI (browser-based stays per ADR-0011)
117
+ - AI-anything
118
+ - Label normalization
119
+ - CONTRIBUTING parsing
120
+ - Multi-host adapters (per Q5; v1.x territory)
121
+ - World map view (per Q1a; post-v1)
122
+
123
+ Flag if anything moves into v1.
124
+
125
+ ---
126
+
127
+ ## Q11. Branding / homepage — DEFER
128
+
129
+ Pre-release polish; revisit during Phase 6.
130
+
131
+ ---
132
+
133
+ ## Q12. CHANGELOG.md / CONTRIBUTING.md / MAINTAINER.md — task list, not decision
134
+
135
+ Folded into ROADMAP Phase 6. No decision needed.
136
+
137
+ ---
138
+
139
+ ## Q13. ~~OAuth App identity for v1 release~~ — ANSWERED 2026-05-03
140
+
141
+ Stay on the personal-account OAuth App for v1.0. Migrate to a dedicated identity when rate limits actually bite. Pragmatic — fits the rest of the v1 scope (don't pay for problems we don't have yet).
142
+
143
+ ---
144
+
145
+ ## Q14. ~~CI / release automation~~ — ANSWERED 2026-05-03
146
+
147
+ GitHub Actions, two workflows:
148
+
149
+ **CI (`ci.yml`)** — runs on push/PR:
150
+ - rubocop
151
+ - rspec
152
+ - integration tests gated behind `GEM_CONTRIBUTE_INTEGRATION=1` (off by default)
153
+ - smoke test: `bundle plugin install` + `gem install` on a clean Ruby image, verify both plugin entry points dispatch a verb
154
+
155
+ **Release (`release.yml`)** — runs on `v*` tag push:
156
+ - **Trusted Publishing via OIDC.** No API key stored as a secret; rubygems.org issues a short-lived token from the GitHub Actions OIDC claim. Compatible with `rubygems_mfa_required = true` (which the gemspec already sets).
157
+ - Setup: configure a trusted publisher entry on rubygems.org (gem name + repo + workflow filename) before the first automated release.
158
+
159
+ Multi-Ruby matrix and signed gems are post-v1.0 unless they become a real ask.
160
+
161
+ ---
162
+
163
+ ## Notes / parking lot
164
+
165
+ - `docs/ideas.md` has one stray idea: "Make sure it respects PR templates" — file as a v1 issue.
166
+ - `docs/index.md` and `docs/_config.yml` suggest a Jekyll site; not yet investigated.
167
+ - `docs/claude-code-prompt.md` — not yet investigated.