gem-contribute 0.1.0
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 +7 -0
- data/.github/ISSUE_TEMPLATE/workshop-issue.md +29 -0
- data/.github/workflows/auto-merge-kicked-tires.yml +88 -0
- data/CHANGELOG.md +24 -0
- data/CLAUDE.md +47 -0
- data/CONTRIBUTING.md +46 -0
- data/KICKED_THE_TIRES.yml +22 -0
- data/LICENSE +21 -0
- data/MAINTAINER.md +92 -0
- data/README.md +89 -0
- data/Rakefile +10 -0
- data/docs/_config.yml +30 -0
- data/docs/adr/0001-just-in-time-auth.md +44 -0
- data/docs/adr/0002-bundler-lockfile-parser.md +35 -0
- data/docs/adr/0003-issue-tracker-preference.md +33 -0
- data/docs/adr/0004-device-flow-auth.md +36 -0
- data/docs/adr/0005-render-labels-verbatim.md +46 -0
- data/docs/adr/0006-standalone-gem-not-plugin.md +31 -0
- data/docs/adr/0007-display-contributing-verbatim.md +39 -0
- data/docs/adr/0008-rooibos-tui-framework.md +62 -0
- data/docs/adr/0009-top-level-namespace.md +37 -0
- data/docs/adr/README.md +21 -0
- data/docs/claude-code-prompt.md +40 -0
- data/docs/design.md +234 -0
- data/docs/index.md +102 -0
- data/docs/prep-plan.md +165 -0
- data/docs/workshop.md +60 -0
- data/exe/gem-contribute +7 -0
- data/lib/gem_contribute/auth.rb +161 -0
- data/lib/gem_contribute/cache.rb +98 -0
- data/lib/gem_contribute/cli/auth.rb +164 -0
- data/lib/gem_contribute/cli/config.rb +87 -0
- data/lib/gem_contribute/cli/fork_clone_branch.rb +197 -0
- data/lib/gem_contribute/cli/issues.rb +123 -0
- data/lib/gem_contribute/cli/scan.rb +117 -0
- data/lib/gem_contribute/cli/submit.rb +155 -0
- data/lib/gem_contribute/cli.rb +104 -0
- data/lib/gem_contribute/config.rb +60 -0
- data/lib/gem_contribute/errors.rb +32 -0
- data/lib/gem_contribute/host_adapter.rb +40 -0
- data/lib/gem_contribute/host_adapters/github_adapter.rb +215 -0
- data/lib/gem_contribute/locked_gem.rb +26 -0
- data/lib/gem_contribute/lockfile_parser.rb +61 -0
- data/lib/gem_contribute/project.rb +21 -0
- data/lib/gem_contribute/resolver.rb +131 -0
- data/lib/gem_contribute/token_store.rb +86 -0
- data/lib/gem_contribute/version.rb +5 -0
- data/lib/gem_contribute.rb +32 -0
- data/script/lint-kicked-tires.rb +76 -0
- data/sig/gem_contribute.rbs +3 -0
- metadata +114 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GemContribute
|
|
4
|
+
# A gem resolved to a host repository.
|
|
5
|
+
#
|
|
6
|
+
# `host` is one of: "github.com", "gitlab.com", "codeberg.org", or :unknown.
|
|
7
|
+
# When :unknown, owner/repo are nil and `metadata` may carry whatever URI we
|
|
8
|
+
# found so the user can at least see it.
|
|
9
|
+
Project = Data.define(:gem_name, :host, :owner, :repo, :metadata) do
|
|
10
|
+
def known_host?
|
|
11
|
+
host.is_a?(String)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def url
|
|
15
|
+
return metadata[:source_url] if metadata && !known_host?
|
|
16
|
+
return nil unless owner && repo
|
|
17
|
+
|
|
18
|
+
"https://#{host}/#{owner}/#{repo}"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "net/http"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module GemContribute
|
|
8
|
+
# Resolves a LockedGem to a Project (host + owner + repo) by querying the
|
|
9
|
+
# RubyGems v1 API and walking the metadata URIs in preference order.
|
|
10
|
+
#
|
|
11
|
+
# Preference order (ADR-0003):
|
|
12
|
+
# bug_tracker_uri → source_code_uri → homepage_uri
|
|
13
|
+
#
|
|
14
|
+
# Recognized hosts: github.com, gitlab.com, codeberg.org. Anything else
|
|
15
|
+
# (mailing-list bug tracker, internal bugzilla, etc.) is stored as the
|
|
16
|
+
# `:source_url` in metadata so the user can at least see it.
|
|
17
|
+
class Resolver
|
|
18
|
+
API_BASE = "https://rubygems.org/api/v1/gems"
|
|
19
|
+
KNOWN_HOSTS = %w[github.com gitlab.com codeberg.org].freeze
|
|
20
|
+
|
|
21
|
+
# Reasons a resolve might come back without a host coordinate. Surfaced as
|
|
22
|
+
# `metadata[:reason]` on the returned Project so the CLI/TUI can show the
|
|
23
|
+
# user *why* a gem wasn't actionable.
|
|
24
|
+
REASON_NON_RUBYGEMS_SOURCE = :non_rubygems_source
|
|
25
|
+
REASON_API_NOT_FOUND = :api_not_found
|
|
26
|
+
REASON_NO_USABLE_URI = :no_usable_uri
|
|
27
|
+
REASON_UNKNOWN_HOST = :unknown_host
|
|
28
|
+
|
|
29
|
+
def initialize(cache: Cache.new, http: Net::HTTP, clock: -> { Time.now.to_i })
|
|
30
|
+
@cache = cache
|
|
31
|
+
@http = http
|
|
32
|
+
@clock = clock
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param gem [LockedGem]
|
|
36
|
+
# @return [Project]
|
|
37
|
+
def resolve(gem)
|
|
38
|
+
return unresolved(gem, REASON_NON_RUBYGEMS_SOURCE) unless gem.resolvable?
|
|
39
|
+
|
|
40
|
+
metadata = fetch_metadata(gem)
|
|
41
|
+
return unresolved(gem, REASON_API_NOT_FOUND) if metadata.nil?
|
|
42
|
+
|
|
43
|
+
uri = preferred_uri(metadata)
|
|
44
|
+
return unresolved(gem, REASON_NO_USABLE_URI) if uri.nil?
|
|
45
|
+
|
|
46
|
+
coords = parse_host_coordinates(uri)
|
|
47
|
+
return unresolved(gem, REASON_UNKNOWN_HOST, source_url: uri) if coords.nil?
|
|
48
|
+
|
|
49
|
+
Project.new(
|
|
50
|
+
gem_name: gem.name,
|
|
51
|
+
host: coords[:host],
|
|
52
|
+
owner: coords[:owner],
|
|
53
|
+
repo: coords[:repo],
|
|
54
|
+
metadata: { source_url: uri, picked_from: coords[:picked_from] }
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def unresolved(gem, reason, **extras)
|
|
61
|
+
Project.new(
|
|
62
|
+
gem_name: gem.name,
|
|
63
|
+
host: :unknown,
|
|
64
|
+
owner: nil,
|
|
65
|
+
repo: nil,
|
|
66
|
+
metadata: { reason: reason, **extras }
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def fetch_metadata(gem)
|
|
71
|
+
cached = @cache.fetch("gems", gem.name)
|
|
72
|
+
return cached if cached
|
|
73
|
+
|
|
74
|
+
response = http_get("#{API_BASE}/#{gem.name}.json")
|
|
75
|
+
case response
|
|
76
|
+
when Net::HTTPSuccess
|
|
77
|
+
@cache.write("gems", gem.name, JSON.parse(response.body))
|
|
78
|
+
when Net::HTTPNotFound
|
|
79
|
+
nil
|
|
80
|
+
else
|
|
81
|
+
raise ResolveError.new(gem.name, "RubyGems API returned #{response.code}")
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def http_get(url)
|
|
86
|
+
uri = URI(url)
|
|
87
|
+
@http.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |conn|
|
|
88
|
+
conn.get(uri.request_uri, "Accept" => "application/json", "User-Agent" => user_agent)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def user_agent
|
|
93
|
+
"gem-contribute/#{GemContribute::VERSION}"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def preferred_uri(metadata)
|
|
97
|
+
# Order matters. ADR-0003.
|
|
98
|
+
candidates = [
|
|
99
|
+
["bug_tracker_uri", metadata["bug_tracker_uri"]],
|
|
100
|
+
["source_code_uri", metadata["source_code_uri"]],
|
|
101
|
+
["homepage_uri", metadata["homepage_uri"]]
|
|
102
|
+
]
|
|
103
|
+
candidates.each do |label, value|
|
|
104
|
+
next if value.nil? || value.empty?
|
|
105
|
+
|
|
106
|
+
@last_picked = label
|
|
107
|
+
return value
|
|
108
|
+
end
|
|
109
|
+
nil
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_host_coordinates(url)
|
|
113
|
+
uri = safe_uri(url)
|
|
114
|
+
return nil if uri.nil? || uri.host.nil? || uri.path.nil?
|
|
115
|
+
|
|
116
|
+
host = uri.host.sub(/\Awww\./, "")
|
|
117
|
+
return nil unless KNOWN_HOSTS.include?(host)
|
|
118
|
+
|
|
119
|
+
owner, repo = uri.path.delete_prefix("/").split("/", 3)
|
|
120
|
+
return nil if owner.to_s.empty? || repo.to_s.empty?
|
|
121
|
+
|
|
122
|
+
{ host: host, owner: owner, repo: repo.sub(/\.git\z/, ""), picked_from: @last_picked }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def safe_uri(url)
|
|
126
|
+
URI.parse(url)
|
|
127
|
+
rescue URI::InvalidURIError
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "json"
|
|
5
|
+
|
|
6
|
+
module GemContribute
|
|
7
|
+
# Reads / writes ~/.config/gem-contribute/auth.json (mode 0600), keyed by
|
|
8
|
+
# host. The per-host structure means GitLab / Codeberg adapters drop in
|
|
9
|
+
# without rearranging storage. See ADR-0001 and ADR-0004.
|
|
10
|
+
#
|
|
11
|
+
# File schema:
|
|
12
|
+
# {
|
|
13
|
+
# "github.com": {
|
|
14
|
+
# "access_token": "gho_...",
|
|
15
|
+
# "scope": "public_repo",
|
|
16
|
+
# "stored_at": 1730000000
|
|
17
|
+
# }
|
|
18
|
+
# }
|
|
19
|
+
#
|
|
20
|
+
# Honors XDG_CONFIG_HOME so tests stay hermetic and unusual layouts work.
|
|
21
|
+
class TokenStore
|
|
22
|
+
def initialize(path: TokenStore.default_path, clock: -> { Time.now.to_i })
|
|
23
|
+
@path = path
|
|
24
|
+
@clock = clock
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [String, nil] the cached access token for the host, or nil
|
|
28
|
+
def token_for(host)
|
|
29
|
+
data = read
|
|
30
|
+
data.dig(host, "access_token")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @return [Hash, nil] {access_token, scope, stored_at} or nil
|
|
34
|
+
def entry_for(host)
|
|
35
|
+
read[host]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def store(host, access_token:, scope: nil)
|
|
39
|
+
data = read
|
|
40
|
+
data[host] = {
|
|
41
|
+
"access_token" => access_token,
|
|
42
|
+
"scope" => scope,
|
|
43
|
+
"stored_at" => @clock.call
|
|
44
|
+
}.compact
|
|
45
|
+
write(data)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def delete(host)
|
|
49
|
+
data = read
|
|
50
|
+
removed = data.delete(host)
|
|
51
|
+
write(data) if removed
|
|
52
|
+
removed
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def hosts
|
|
56
|
+
read.keys
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def self.default_path
|
|
60
|
+
base = ENV["XDG_CONFIG_HOME"] || File.expand_path("~/.config")
|
|
61
|
+
File.join(base, "gem-contribute", "auth.json")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def read
|
|
67
|
+
return {} unless File.file?(@path)
|
|
68
|
+
|
|
69
|
+
JSON.parse(File.read(@path, encoding: "UTF-8"))
|
|
70
|
+
rescue JSON::ParserError, Encoding::InvalidByteSequenceError
|
|
71
|
+
# Corrupt store: don't lose the user's data, but don't crash either.
|
|
72
|
+
# The token is recoverable by re-running `auth login`; the worst case
|
|
73
|
+
# is one extra device-flow round trip.
|
|
74
|
+
{}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def write(data)
|
|
78
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
79
|
+
tmp = "#{@path}.tmp"
|
|
80
|
+
File.write(tmp, JSON.pretty_generate(data), encoding: "UTF-8")
|
|
81
|
+
File.chmod(0o600, tmp)
|
|
82
|
+
File.rename(tmp, @path)
|
|
83
|
+
File.chmod(0o600, @path)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "gem_contribute/version"
|
|
4
|
+
require_relative "gem_contribute/errors"
|
|
5
|
+
|
|
6
|
+
module GemContribute
|
|
7
|
+
autoload :LockedGem, "gem_contribute/locked_gem"
|
|
8
|
+
autoload :Project, "gem_contribute/project"
|
|
9
|
+
|
|
10
|
+
# The canonical Project for gem-contribute itself. Used by the CLI to
|
|
11
|
+
# short-circuit resolution (gem-contribute isn't on RubyGems yet) and
|
|
12
|
+
# to auto-inject the tool into its own scan results.
|
|
13
|
+
SELF_PROJECT = Project.new(
|
|
14
|
+
gem_name: "gem-contribute",
|
|
15
|
+
host: "github.com",
|
|
16
|
+
owner: "cdhagmann",
|
|
17
|
+
repo: "gem-contribute",
|
|
18
|
+
metadata: { self_injected: true }
|
|
19
|
+
).freeze
|
|
20
|
+
autoload :LockfileParser, "gem_contribute/lockfile_parser"
|
|
21
|
+
autoload :Cache, "gem_contribute/cache"
|
|
22
|
+
autoload :Resolver, "gem_contribute/resolver"
|
|
23
|
+
autoload :HostAdapter, "gem_contribute/host_adapter"
|
|
24
|
+
autoload :Auth, "gem_contribute/auth"
|
|
25
|
+
autoload :Config, "gem_contribute/config"
|
|
26
|
+
autoload :TokenStore, "gem_contribute/token_store"
|
|
27
|
+
autoload :CLI, "gem_contribute/cli"
|
|
28
|
+
|
|
29
|
+
module HostAdapters
|
|
30
|
+
autoload :GitHubAdapter, "gem_contribute/host_adapters/github_adapter"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Validates KICKED_THE_TIRES.yml against the schema documented in its header.
|
|
5
|
+
# Used by the auto-merge workflow (.github/workflows/auto-merge-kicked-tires.yml)
|
|
6
|
+
# and runnable locally:
|
|
7
|
+
#
|
|
8
|
+
# ruby script/lint-kicked-tires.rb # lint the canonical file
|
|
9
|
+
# ruby script/lint-kicked-tires.rb path.yml # lint a specific file
|
|
10
|
+
#
|
|
11
|
+
# Exits 0 on success, 1 on any schema violation, with a clear message.
|
|
12
|
+
|
|
13
|
+
require "yaml"
|
|
14
|
+
require "date"
|
|
15
|
+
|
|
16
|
+
PATH = ARGV[0] || "KICKED_THE_TIRES.yml"
|
|
17
|
+
ALLOWED_KEYS = %w[handle date note location].freeze
|
|
18
|
+
REQUIRED_KEYS = %w[handle date].freeze
|
|
19
|
+
HANDLE_PATTERN = /\A[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,38}[a-zA-Z0-9])?\z/ # GitHub handle rules
|
|
20
|
+
DATE_PATTERN = /\A\d{4}-\d{2}-\d{2}\z/
|
|
21
|
+
MAX_BYTES = 100_000 # 100KB — guards against runaway PRs
|
|
22
|
+
|
|
23
|
+
def fail!(msg)
|
|
24
|
+
warn "✗ #{msg}"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
fail! "file not found: #{PATH}" unless File.exist?(PATH)
|
|
29
|
+
fail! "file is suspiciously large (#{File.size(PATH)} bytes)" if File.size(PATH) > MAX_BYTES
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
data = YAML.safe_load_file(PATH, permitted_classes: [Date])
|
|
33
|
+
rescue Psych::SyntaxError => e
|
|
34
|
+
fail! "YAML syntax error: #{e.message}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
fail! "top-level must be an array" unless data.is_a?(Array)
|
|
38
|
+
fail! "must have at least one entry" if data.empty?
|
|
39
|
+
|
|
40
|
+
handles = []
|
|
41
|
+
data.each_with_index do |entry, i|
|
|
42
|
+
num = i + 1
|
|
43
|
+
fail! "entry #{num}: must be a hash, got #{entry.class}" unless entry.is_a?(Hash)
|
|
44
|
+
|
|
45
|
+
keys = entry.keys.map(&:to_s)
|
|
46
|
+
unknown = keys - ALLOWED_KEYS
|
|
47
|
+
fail! "entry #{num}: unknown keys: #{unknown.inspect} (allowed: #{ALLOWED_KEYS.inspect})" \
|
|
48
|
+
unless unknown.empty?
|
|
49
|
+
|
|
50
|
+
missing = REQUIRED_KEYS - keys
|
|
51
|
+
fail! "entry #{num}: missing required keys: #{missing.inspect}" unless missing.empty?
|
|
52
|
+
|
|
53
|
+
handle = entry["handle"]
|
|
54
|
+
fail! "entry #{num}: handle must be a string" unless handle.is_a?(String)
|
|
55
|
+
fail! "entry #{num}: handle must not start with '@' (use just the username)" \
|
|
56
|
+
if handle.start_with?("@")
|
|
57
|
+
fail! "entry #{num}: handle #{handle.inspect} doesn't look like a GitHub username" \
|
|
58
|
+
unless handle.match?(HANDLE_PATTERN)
|
|
59
|
+
|
|
60
|
+
date = entry["date"]
|
|
61
|
+
date_ok = date.is_a?(Date) || (date.is_a?(String) && date.match?(DATE_PATTERN))
|
|
62
|
+
fail! "entry #{num}: date must be YYYY-MM-DD" unless date_ok
|
|
63
|
+
|
|
64
|
+
fail! "entry #{num}: note must be a string" if entry.key?("note") && !entry["note"].is_a?(String)
|
|
65
|
+
|
|
66
|
+
if entry.key?("location") && !entry["location"].is_a?(String)
|
|
67
|
+
fail! "entry #{num}: location must be a string (not a nested object)"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
handles << handle
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
duplicates = handles.group_by(&:itself).select { |_, v| v.size > 1 }.keys
|
|
74
|
+
fail! "duplicate handles: #{duplicates.inspect}" unless duplicates.empty?
|
|
75
|
+
|
|
76
|
+
puts "✓ #{data.size} entries, all valid"
|
metadata
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: gem-contribute
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Chris Hagmann
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: bundler
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.4'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.4'
|
|
26
|
+
description: |
|
|
27
|
+
gem-contribute reads a project's Gemfile.lock, resolves each gem's source
|
|
28
|
+
repository via the RubyGems API, surfaces open contributable issues from
|
|
29
|
+
those repositories, and offers a one-keystroke fork-clone-branch flow so a
|
|
30
|
+
developer can go from "I noticed an issue" to "I have a working branch" in
|
|
31
|
+
seconds. v0.1 supports GitHub-hosted gems with OAuth device-flow auth.
|
|
32
|
+
email:
|
|
33
|
+
- cdhagmann@gmail.com
|
|
34
|
+
executables:
|
|
35
|
+
- gem-contribute
|
|
36
|
+
extensions: []
|
|
37
|
+
extra_rdoc_files: []
|
|
38
|
+
files:
|
|
39
|
+
- ".github/ISSUE_TEMPLATE/workshop-issue.md"
|
|
40
|
+
- ".github/workflows/auto-merge-kicked-tires.yml"
|
|
41
|
+
- CHANGELOG.md
|
|
42
|
+
- CLAUDE.md
|
|
43
|
+
- CONTRIBUTING.md
|
|
44
|
+
- KICKED_THE_TIRES.yml
|
|
45
|
+
- LICENSE
|
|
46
|
+
- MAINTAINER.md
|
|
47
|
+
- README.md
|
|
48
|
+
- Rakefile
|
|
49
|
+
- docs/_config.yml
|
|
50
|
+
- docs/adr/0001-just-in-time-auth.md
|
|
51
|
+
- docs/adr/0002-bundler-lockfile-parser.md
|
|
52
|
+
- docs/adr/0003-issue-tracker-preference.md
|
|
53
|
+
- docs/adr/0004-device-flow-auth.md
|
|
54
|
+
- docs/adr/0005-render-labels-verbatim.md
|
|
55
|
+
- docs/adr/0006-standalone-gem-not-plugin.md
|
|
56
|
+
- docs/adr/0007-display-contributing-verbatim.md
|
|
57
|
+
- docs/adr/0008-rooibos-tui-framework.md
|
|
58
|
+
- docs/adr/0009-top-level-namespace.md
|
|
59
|
+
- docs/adr/README.md
|
|
60
|
+
- docs/claude-code-prompt.md
|
|
61
|
+
- docs/design.md
|
|
62
|
+
- docs/index.md
|
|
63
|
+
- docs/prep-plan.md
|
|
64
|
+
- docs/workshop.md
|
|
65
|
+
- exe/gem-contribute
|
|
66
|
+
- lib/gem_contribute.rb
|
|
67
|
+
- lib/gem_contribute/auth.rb
|
|
68
|
+
- lib/gem_contribute/cache.rb
|
|
69
|
+
- lib/gem_contribute/cli.rb
|
|
70
|
+
- lib/gem_contribute/cli/auth.rb
|
|
71
|
+
- lib/gem_contribute/cli/config.rb
|
|
72
|
+
- lib/gem_contribute/cli/fork_clone_branch.rb
|
|
73
|
+
- lib/gem_contribute/cli/issues.rb
|
|
74
|
+
- lib/gem_contribute/cli/scan.rb
|
|
75
|
+
- lib/gem_contribute/cli/submit.rb
|
|
76
|
+
- lib/gem_contribute/config.rb
|
|
77
|
+
- lib/gem_contribute/errors.rb
|
|
78
|
+
- lib/gem_contribute/host_adapter.rb
|
|
79
|
+
- lib/gem_contribute/host_adapters/github_adapter.rb
|
|
80
|
+
- lib/gem_contribute/locked_gem.rb
|
|
81
|
+
- lib/gem_contribute/lockfile_parser.rb
|
|
82
|
+
- lib/gem_contribute/project.rb
|
|
83
|
+
- lib/gem_contribute/resolver.rb
|
|
84
|
+
- lib/gem_contribute/token_store.rb
|
|
85
|
+
- lib/gem_contribute/version.rb
|
|
86
|
+
- script/lint-kicked-tires.rb
|
|
87
|
+
- sig/gem_contribute.rbs
|
|
88
|
+
homepage: https://cdhagmann.com/gem-contribute/
|
|
89
|
+
licenses:
|
|
90
|
+
- MIT
|
|
91
|
+
metadata:
|
|
92
|
+
source_code_uri: https://github.com/cdhagmann/gem-contribute
|
|
93
|
+
bug_tracker_uri: https://github.com/cdhagmann/gem-contribute/issues
|
|
94
|
+
changelog_uri: https://github.com/cdhagmann/gem-contribute/blob/main/CHANGELOG.md
|
|
95
|
+
documentation_uri: https://cdhagmann.com/gem-contribute/
|
|
96
|
+
rubygems_mfa_required: 'true'
|
|
97
|
+
rdoc_options: []
|
|
98
|
+
require_paths:
|
|
99
|
+
- lib
|
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: 3.2.0
|
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
110
|
+
requirements: []
|
|
111
|
+
rubygems_version: 4.0.10
|
|
112
|
+
specification_version: 4
|
|
113
|
+
summary: Find and contribute to the open-source Ruby gems your project depends on.
|
|
114
|
+
test_files: []
|