geet 0.30.1 → 0.31.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 17e1a6a9c31e72000fc09feb1cec44ed731bf86f645133ba90f21448bd7a73eb
4
- data.tar.gz: 46ce4484514700a897b8558062f7bb2729b1863a2135fb05007c5854fbed37b9
3
+ metadata.gz: 959d270dbe83292c276b920e22995c6a901cfe149be68595d35ab8917dc9b527
4
+ data.tar.gz: 5666cf9e1f33a5d8d09aed56049d4839f13daf984894ce4fc6acf4d2527ae7b5
5
5
  SHA512:
6
- metadata.gz: 72fe78de09b97764add24958707a3adb96e0cbdebfc52bf8f4234a625a6763c740ca39d3045e40e583f820121b784fdf25d38c97ae6993955a89e3876be87846
7
- data.tar.gz: 7c84aab8d706442305bc4f12d629040a9345b253efdaa6ed44d64591056664d2780b739e28f2dac299f5a0d2d69dcc4fe6bc26744f657d967572ddc474f438b1
6
+ metadata.gz: 316d184df8c655265eea18be05c60a29bd22eb5c90b4c3e6f7d97a737e84f3d95e861dc7744a7a1d19473dff74c41549af719c8976f4139c87085d82b947203c
7
+ data.tar.gz: d44f2acbb7fb8f33fa611bacd59b3ebe4001739bdfc1a10497c4fe36f3de4a4e53cc642809d4547be9837c97df56eb6650796f40dcf82c6baeec0559947ff6c9
@@ -7,10 +7,10 @@ jobs:
7
7
  name: Test Suite
8
8
  runs-on: ubuntu-latest
9
9
  env:
10
- GITHUB_API_TOKEN: foo
10
+ GH_TOKEN: foo
11
11
  strategy:
12
12
  matrix:
13
- ruby-version: [head, 4.0, 3.4, 3.3, 3.2]
13
+ ruby-version: [head, 4.0, 3.4, 3.3]
14
14
  fail-fast: false
15
15
  continue-on-error: ${{ endsWith(matrix['ruby-version'], 'head') }}
16
16
  steps:
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.4.6
1
+ 4.0.5
data/README.md CHANGED
@@ -12,7 +12,7 @@ Everything is tracked in detail via [issues](https://github.com/saveriomiroddi/g
12
12
 
13
13
  ## Supported operations
14
14
 
15
- - create label, gist, issue, milestone, PR
15
+ - create label, gist, issue, milestone, PR, repository, repository fork
16
16
  - list issues, labels, milestones, PRs
17
17
  - merge PR, comment PR
18
18
  - open repository
@@ -27,6 +27,22 @@ Geet requires the API token environment variable to be set, eg:
27
27
 
28
28
  All the commands need to be run from the git repository.
29
29
 
30
+ ### Create a repository or fork
31
+
32
+ Create a repository and select its visibility interactively:
33
+
34
+ $ geet repo create
35
+
36
+ Create a private repository without a prompt:
37
+
38
+ $ geet repo create --visibility private
39
+
40
+ Create a fork while preserving the source as `upstream`:
41
+
42
+ $ geet repo create --upstream git@github.com:owner/project.git
43
+
44
+ Geet configures the created repository's SSH address as `origin`, preserves the source address as `upstream` when forking, pushes the current branch, and updates the GitHub default branch to match it.
45
+
30
46
  ### Create an issue (with label and assignees)
31
47
 
32
48
  Basic creation of an issue:
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ require "bundler/gem_tasks"
1
2
  require "rspec/core/rake_task"
2
3
 
3
4
  RSpec::Core::RakeTask.new(:spec) do |task|
data/bin/geet CHANGED
@@ -16,6 +16,16 @@ class GeetLauncher
16
16
  def launch
17
17
  command, options = Commandline::Configuration.new.decode_argv || exit
18
18
 
19
+ if command == REPO_CREATE_COMMAND
20
+ Services::CreateRepo.new.execute(**options)
21
+ return
22
+ end
23
+
24
+ if command == REPO_OPEN_COMMAND
25
+ Services::OpenRepo.new.execute(**options)
26
+ return
27
+ end
28
+
19
29
  repository = Git::Repository.new(upstream: !!options[:upstream], protected_repositories: protected_repositories)
20
30
 
21
31
  case command
@@ -75,8 +85,6 @@ class GeetLauncher
75
85
  Services::OpenPr.new(repository).execute(**options)
76
86
  when REPO_ADD_UPSTREAM_COMMAND
77
87
  Services::AddUpstreamRepo.new(repository).execute
78
- when REPO_OPEN_COMMAND
79
- Services::OpenRepo.new(repository).execute(**options)
80
88
  else
81
89
  raise "Internal error - Unrecognized command #{command.inspect}"
82
90
  end
@@ -129,4 +137,4 @@ class GeetLauncher
129
137
  end
130
138
  end
131
139
 
132
- GeetLauncher.new.launch
140
+ GeetLauncher.new.launch if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,89 @@
1
+ # Pre-existing Build and CI Fixes Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Align CI with the project's `GH_TOKEN` contract and expose Bundler's standard gem build tasks through Rake.
6
+
7
+ **Architecture:** Correct the existing configuration at its two sources: the CI environment declaration and the Rake task imports. Preserve the application's established token interface and use Bundler's provided gem tasks without custom wrappers.
8
+
9
+ **Tech Stack:** Ruby 4.0.5, Rake, Bundler, GitHub Actions, RSpec, Sorbet, RuboCop
10
+
11
+ ## Global Constraints
12
+
13
+ - CI supplies the token as `GH_TOKEN`.
14
+ - `Rakefile` loads `bundler/gem_tasks`.
15
+ - Do not add token aliases, fallback credentials, dependency updates, or unrelated build changes.
16
+
17
+ ---
18
+
19
+ ### Task 1: Correct the CI token and Rake gem tasks
20
+
21
+ **Files:**
22
+ - Modify: `.github/workflows/ci.yml`
23
+ - Modify: `Rakefile`
24
+
25
+ **Interfaces:**
26
+ - Consumes: the application's established `GH_TOKEN` environment-variable contract and Bundler's `gem_tasks` API
27
+ - Produces: a CI test job that satisfies the test harness and standard `rake build` and release tasks
28
+
29
+ - [ ] **Step 1: Demonstrate both pre-existing failures**
30
+
31
+ Run:
32
+
33
+ ```bash
34
+ env -u GH_TOKEN GITHUB_API_TOKEN=foo bundle exec rspec \
35
+ && exit 1 \
36
+ || test "$?" -eq 1
37
+ bundle exec rake build
38
+ ```
39
+
40
+ Expected: RSpec fails because `GH_TOKEN` is absent, and Rake fails with `Don't know how to build task 'build'`.
41
+
42
+ - [ ] **Step 2: Correct the configuration**
43
+
44
+ In `.github/workflows/ci.yml`, set the test-suite environment to:
45
+
46
+ ```yaml
47
+ env:
48
+ GH_TOKEN: foo
49
+ ```
50
+
51
+ At the beginning of `Rakefile`, add:
52
+
53
+ ```ruby
54
+ require "bundler/gem_tasks"
55
+ ```
56
+
57
+ - [ ] **Step 3: Run focused verification**
58
+
59
+ Run:
60
+
61
+ ```bash
62
+ GH_TOKEN=foo bundle exec rspec \
63
+ && bundle exec rake build
64
+ ```
65
+
66
+ Expected: 39 examples pass with no failures, and `pkg/geet-0.30.1.gem` is built.
67
+
68
+ - [ ] **Step 4: Run full verification and clean generated output**
69
+
70
+ Run:
71
+
72
+ ```bash
73
+ GH_TOKEN=foo bundle exec rake \
74
+ && bundle exec srb typecheck \
75
+ && bundle exec rubocop \
76
+ && rm -f pkg/geet-0.30.1.gem \
77
+ && rmdir pkg \
78
+ && git diff --check \
79
+ && git status --short
80
+ ```
81
+
82
+ Expected: the default Rake task passes 39 examples, Sorbet reports no errors, RuboCop reports no offenses, generated packaging output is removed, and only `.github/workflows/ci.yml` and `Rakefile` are modified.
83
+
84
+ - [ ] **Step 5: Commit the fixes**
85
+
86
+ ```bash
87
+ git add .github/workflows/ci.yml Rakefile \
88
+ && git commit -m "Fix CI token and gem build tasks"
89
+ ```
@@ -0,0 +1,435 @@
1
+ # Repository Creation Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Add `geet repo create` to create or fork a GitHub repository from the current directory, configure remotes, push the current branch, and make it the default branch.
6
+
7
+ **Architecture:** A new `Services::CreateRepo` owns the workflow without constructing `Git::Repository`. A focused `Github::Repository` models repository API operations, while `Utils::GitClient` provides the two missing local queries. The existing command decoder and launcher expose the service.
8
+
9
+ **Tech Stack:** Ruby 4.0, Sorbet runtime signatures, RSpec, WebMock/VCR-compatible GitHub REST calls, TTY::Prompt, Git CLI.
10
+
11
+ ## Global Constraints
12
+
13
+ - Work only in `/home/saverio/code/geet-repo-create` on branch `repo-create`.
14
+ - The repository name is the basename of the current directory.
15
+ - Generate `origin` as `git@github.com:OWNER/REPOSITORY.git`.
16
+ - Preserve the supplied `--upstream` address unchanged for the local `upstream` remote.
17
+ - Reject an existing `origin`; in fork mode also reject an existing `upstream`.
18
+ - Reject `--visibility` with `--upstream`; otherwise accept only `private` or `public` and prompt when omitted.
19
+ - Push the current branch with upstream tracking before setting it as GitHub's default branch.
20
+ - Do not roll back a created GitHub repository or configured remote after a later failure.
21
+ - Run RSpec with `GH_TOKEN=test` so recorded integration tests can initialize their API clients.
22
+ - Do not use conventional-commit prefixes in commit titles.
23
+
24
+ ---
25
+
26
+ ### Task 1: GitHub repository API object
27
+
28
+ **Files:**
29
+ - Create: `lib/geet/github/repository.rb`
30
+ - Create: `spec/unit/github/repository_spec.rb`
31
+
32
+ **Interfaces:**
33
+ - Consumes: `Geet::Github::ApiInterface#send_request(api_path, data:, http_method:)`.
34
+ - Produces: `Github::Repository.create(name, visibility, api_interface)`, `.fork(upstream_path, name, api_interface)`, `.fetch(path, api_interface)`, and `#update_default_branch(branch)`; instances expose `full_name`, `html_url`, and `ssh_url`.
35
+
36
+ - [ ] **Step 1: Write failing API-object specs**
37
+
38
+ Create `spec/unit/github/repository_spec.rb` with examples that use an `instance_double(Geet::Github::ApiInterface)` and assert these exact requests and results:
39
+
40
+ ```ruby
41
+ # frozen_string_literal: true
42
+
43
+ require_relative "../../spec_helper"
44
+
45
+ RSpec.describe Geet::Github::Repository do
46
+ let(:api_interface) { instance_double(Geet::Github::ApiInterface) }
47
+ let(:response) do
48
+ {
49
+ "full_name" => "donald/geet",
50
+ "html_url" => "https://github.com/donald/geet",
51
+ "ssh_url" => "git@github.com:donald/geet.git",
52
+ }
53
+ end
54
+
55
+ it "creates a private repository for the authenticated user" do
56
+ expect(api_interface).to receive(:send_request).with("/user/repos", data: {name: "geet", private: true}).and_return(response)
57
+ repository = described_class.create("geet", "private", api_interface)
58
+ expect([repository.full_name, repository.html_url, repository.ssh_url]).to eq(["donald/geet", "https://github.com/donald/geet", "git@github.com:donald/geet.git"])
59
+ end
60
+
61
+ it "creates a public repository for the authenticated user" do
62
+ expect(api_interface).to receive(:send_request).with("/user/repos", data: {name: "geet", private: false}).and_return(response)
63
+ described_class.create("geet", "public", api_interface)
64
+ end
65
+
66
+ it "requests a named fork" do
67
+ expect(api_interface).to receive(:send_request).with("/repos/upstream/project/forks", data: {name: "geet"}).and_return(response)
68
+ described_class.fork("upstream/project", "geet", api_interface)
69
+ end
70
+
71
+ it "fetches a repository" do
72
+ expect(api_interface).to receive(:send_request).with("/repos/donald/geet").and_return(response)
73
+ expect(described_class.fetch("donald/geet", api_interface).full_name).to eq("donald/geet")
74
+ end
75
+
76
+ it "updates the default branch" do
77
+ expect(api_interface).to receive(:send_request).with("/repos/donald/geet", data: {default_branch: "topic"}, http_method: :patch)
78
+ described_class.new(response, api_interface).update_default_branch("topic")
79
+ end
80
+ end
81
+ ```
82
+
83
+ - [ ] **Step 2: Run the specs and verify the missing constant failure**
84
+
85
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/github/repository_spec.rb`
86
+
87
+ Expected: failure because `Geet::Github::Repository` is not defined.
88
+
89
+ - [ ] **Step 3: Implement the repository API object**
90
+
91
+ Create `lib/geet/github/repository.rb` with strict Sorbet signatures, readers for the three response strings, a constructor that extracts them with `fetch`, the three class methods using the exact API paths above, and:
92
+
93
+ ```ruby
94
+ sig { params(branch: String).void }
95
+ def update_default_branch(branch)
96
+ @api_interface.send_request("/repos/#{@full_name}", data: {default_branch: branch}, http_method: :patch)
97
+ end
98
+ ```
99
+
100
+ Use a private `from_response` class helper so `create`, `fork`, and `fetch` each cast the response once and construct the object without duplicating extraction logic.
101
+
102
+ - [ ] **Step 4: Run the API-object specs**
103
+
104
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/github/repository_spec.rb`
105
+
106
+ Expected: 5 examples, 0 failures.
107
+
108
+ - [ ] **Step 5: Commit the API object**
109
+
110
+ ```bash
111
+ git add lib/geet/github/repository.rb spec/unit/github/repository_spec.rb
112
+ git commit -m "Add GitHub repository API operations"
113
+ ```
114
+
115
+ ### Task 2: Local Git preflight queries and address parsing
116
+
117
+ **Files:**
118
+ - Modify: `lib/geet/utils/git_client.rb`
119
+ - Create: `spec/unit/utils/git_client_spec.rb`
120
+
121
+ **Interfaces:**
122
+ - Consumes: `Utils::GitClient#execute_git_command` and the existing `REMOTE_URL_REGEX`.
123
+ - Produces: `Utils::GitClient#head_commit? -> T::Boolean` and `#remote_path(remote_url: String) -> String`.
124
+
125
+ - [ ] **Step 1: Write failing GitClient specs**
126
+
127
+ Create `spec/unit/utils/git_client_spec.rb`. Instantiate `GitClient`, stub its private `execute_git_command`, and cover:
128
+
129
+ ```ruby
130
+ describe "#head_commit?" do
131
+ it "returns true when HEAD resolves" do
132
+ allow(subject).to receive(:execute_git_command).with("rev-parse --verify HEAD", allow_error: true, silent_stderr: true).and_return("abc123")
133
+ expect(subject.head_commit?).to be(true)
134
+ end
135
+
136
+ it "returns false for an unborn HEAD" do
137
+ allow(subject).to receive(:execute_git_command).with("rev-parse --verify HEAD", allow_error: true, silent_stderr: true).and_return("")
138
+ expect(subject.head_commit?).to be(false)
139
+ end
140
+ end
141
+
142
+ describe "#remote_path" do
143
+ it "extracts an SSH repository path" do
144
+ expect(subject.remote_path("git@github.com:owner/project.git")).to eq("owner/project")
145
+ end
146
+
147
+ it "extracts an HTTPS repository path" do
148
+ expect(subject.remote_path("https://github.com/owner/project.git")).to eq("owner/project")
149
+ end
150
+
151
+ it "rejects an unsupported address" do
152
+ expect { subject.remote_path("owner/project") }.to raise_error(RuntimeError, 'Unexpected remote reference format: "owner/project"')
153
+ end
154
+ end
155
+ ```
156
+
157
+ - [ ] **Step 2: Run the specs and verify missing-method failures**
158
+
159
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/utils/git_client_spec.rb`
160
+
161
+ Expected: failures reporting undefined `head_commit?` and `remote_path`.
162
+
163
+ - [ ] **Step 3: Implement the two queries and reuse address parsing**
164
+
165
+ Add:
166
+
167
+ ```ruby
168
+ sig { returns(T::Boolean) }
169
+ def head_commit?
170
+ execute_git_command("rev-parse --verify HEAD", allow_error: true, silent_stderr: true) != ""
171
+ end
172
+
173
+ sig { params(remote_url: String).returns(String) }
174
+ def remote_path(remote_url)
175
+ match = remote_url.match(REMOTE_URL_REGEX)
176
+ raise "Unexpected remote reference format: #{remote_url.inspect}" if !match
177
+ T.must(match[4])
178
+ end
179
+ ```
180
+
181
+ Change `#path` to call `remote_path(remote(**remote_name_option))`, and change `#remote` to validate by calling `remote_path(remote_url)` after the existing missing-remote check. Do not expand the accepted URL grammar in this task.
182
+
183
+ - [ ] **Step 4: Run focused and full tests**
184
+
185
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/utils/git_client_spec.rb spec/integration/open_repo_spec.rb`
186
+
187
+ Expected: 7 examples, 0 failures.
188
+
189
+ Run: `GH_TOKEN=test bundle exec rake`
190
+
191
+ Expected: all examples pass.
192
+
193
+ - [ ] **Step 5: Commit the Git primitives**
194
+
195
+ ```bash
196
+ git add lib/geet/utils/git_client.rb spec/unit/utils/git_client_spec.rb
197
+ git commit -m "Add repository creation Git queries"
198
+ ```
199
+
200
+ ### Task 3: Repository creation workflow
201
+
202
+ **Files:**
203
+ - Create: `lib/geet/services/create_repo.rb`
204
+ - Create: `spec/unit/services/create_repo_spec.rb`
205
+
206
+ **Interfaces:**
207
+ - Consumes: Task 1's `Github::Repository` methods; Task 2's `GitClient#head_commit?` and `#remote_path`; existing `#current_branch`, `#remote_defined?`, `#add_remote`, and `#push`.
208
+ - Produces: `Services::CreateRepo#execute(visibility: T.nilable(String), upstream: T.nilable(String)) -> Github::Repository`.
209
+
210
+ - [ ] **Step 1: Write failing preflight and visibility specs**
211
+
212
+ Build `spec/unit/services/create_repo_spec.rb` around doubles for `GitClient`, `Github::ApiInterface`, and `TTY::Prompt`. Construct the service with injectable `directory: "/tmp/geet"`, `out: StringIO.new`, `git_client:`, `api_interface:`, `prompt:`, `sleeper: ->(_) {}` and `fork_attempts: 3`.
213
+
214
+ Add examples asserting that no API request occurs and the exact errors are raised for:
215
+
216
+ ```ruby
217
+ "The repository has no commits!"
218
+ "Remote \"origin\" already exists!"
219
+ "Remote \"upstream\" already exists!"
220
+ "Visibility must be private or public!"
221
+ "Visibility can't be specified with upstream!"
222
+ ```
223
+
224
+ The common valid preflight stubs are `head_commit? => true`, `current_branch => "topic"`, `remote_defined?("origin") => false`, and, only in fork mode, `remote_defined?("upstream") => false`.
225
+
226
+ Add one example where `visibility` is nil, `prompt.select("Visibility:", ["private", "public"])` returns `"private"`, and the normal create path receives `"private"`. Add one example showing explicit `"public"` does not call the prompt.
227
+
228
+ - [ ] **Step 2: Run the preflight specs and verify the missing service failure**
229
+
230
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
231
+
232
+ Expected: failure because `Geet::Services::CreateRepo` is not defined.
233
+
234
+ - [ ] **Step 3: Implement initialization and preflight**
235
+
236
+ Create the strict service with these defaults and injected types:
237
+
238
+ ```ruby
239
+ VISIBILITIES = T.let(["private", "public"].freeze, T::Array[String])
240
+ FORK_ATTEMPTS = 30
241
+ FORK_RETRY_DELAY = 1
242
+
243
+ def initialize(directory: Dir.pwd, out: $stdout, git_client: Utils::GitClient.new, api_interface: nil, prompt: TTY::Prompt.new, sleeper: Kernel.method(:sleep), fork_attempts: FORK_ATTEMPTS)
244
+ @directory = directory
245
+ @out = out
246
+ @git_client = git_client
247
+ @api_interface = api_interface || Github::ApiInterface.new(ENV["GH_TOKEN"] || raise("GH_TOKEN not set!"))
248
+ @prompt = prompt
249
+ @sleeper = sleeper
250
+ @fork_attempts = fork_attempts
251
+ end
252
+ ```
253
+
254
+ `execute` must call preflight before prompting or making API calls. Derive `name = File.basename(@directory)` and `branch = @git_client.current_branch`. Keep validation in short private methods with the exact error text from Step 1.
255
+
256
+ - [ ] **Step 4: Run preflight specs**
257
+
258
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
259
+
260
+ Expected: all preflight and prompt examples pass; workflow examples added next are still absent.
261
+
262
+ - [ ] **Step 5: Add failing normal-workflow and fork-workflow specs**
263
+
264
+ For normal creation, stub `Github::Repository.create("geet", "private", api_interface)` to return a repository double with `full_name: "donald/geet"`, `ssh_url: "git@github.com:donald/geet.git"`, and `html_url: "https://github.com/donald/geet"`. Assert ordered calls to add `origin`, push `"topic"`, and update the default branch.
265
+
266
+ For fork creation, use upstream `https://github.com/upstream/project.git`, assert `remote_path` returns `"upstream/project"`, assert `Github::Repository.fork("upstream/project", "geet", api_interface)` returns a provisional repository with `full_name: "donald/geet"`, then make `.fetch("donald/geet", api_interface)` first raise `Shared::HttpError.new("Not Found", 404)` and then return the available repository. Assert the sleeper receives `1`, the exact supplied URL is added as `upstream`, SSH `origin` is added, and push/default-branch calls follow.
267
+
268
+ Add a timeout example where all three fetches raise 404 and expect `"Timed out waiting for fork donald/geet"`. Add a non-404 fetch error example and expect it to propagate immediately. Add a failure-order example proving an `add_remote` error prevents push and default-branch update. Assert success output contains both `Repository address: https://github.com/donald/geet` and `Default branch: topic`.
269
+
270
+ - [ ] **Step 6: Implement normal creation, fork waiting, remotes, push, and output**
271
+
272
+ Implement the workflow in this order:
273
+
274
+ ```ruby
275
+ repository = upstream ? create_fork(name, upstream) : Github::Repository.create(name, selected_visibility, @api_interface)
276
+ @git_client.add_remote(Utils::GitClient::ORIGIN_NAME, repository.ssh_url)
277
+ @git_client.add_remote(Utils::GitClient::UPSTREAM_NAME, upstream) if upstream
278
+ @git_client.push(remote_branch: branch)
279
+ repository.update_default_branch(branch)
280
+ @out.puts "Repository address: #{repository.html_url}"
281
+ @out.puts "Default branch: #{branch}"
282
+ repository
283
+ ```
284
+
285
+ `create_fork` parses the upstream, requests the fork, then calls `wait_for_fork(full_name)`. `wait_for_fork` attempts `Github::Repository.fetch` up to `@fork_attempts` times, rescues only `Shared::HttpError` with code 404, sleeps `FORK_RETRY_DELAY` between attempts but not after the final attempt, and raises the exact timeout error after exhaustion.
286
+
287
+ - [ ] **Step 7: Run service and full tests**
288
+
289
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/services/create_repo_spec.rb`
290
+
291
+ Expected: all examples pass.
292
+
293
+ Run: `GH_TOKEN=test bundle exec rake`
294
+
295
+ Expected: all examples pass.
296
+
297
+ - [ ] **Step 8: Commit the service**
298
+
299
+ ```bash
300
+ git add lib/geet/services/create_repo.rb spec/unit/services/create_repo_spec.rb
301
+ git commit -m "Add repository creation workflow"
302
+ ```
303
+
304
+ ### Task 4: CLI integration and user documentation
305
+
306
+ **Files:**
307
+ - Modify: `lib/geet/commandline/commands.rb`
308
+ - Modify: `lib/geet/commandline/configuration.rb`
309
+ - Modify: `bin/geet`
310
+ - Create: `spec/unit/commandline/configuration_spec.rb`
311
+ - Create: `spec/unit/geet_launcher_spec.rb`
312
+ - Modify: `README.md`
313
+
314
+ **Interfaces:**
315
+ - Consumes: `Services::CreateRepo#execute(visibility:, upstream:)` from Task 3.
316
+ - Produces: `REPO_CREATE_COMMAND = "repo.create"` and CLI syntax `geet repo create [-v|--visibility private|public] [-u|--upstream ADDRESS]`.
317
+
318
+ - [ ] **Step 1: Write failing command-decoding specs**
319
+
320
+ In `spec/unit/commandline/configuration_spec.rb`, stub `ARGV` via `stub_const("ARGV", [...])` and assert:
321
+
322
+ ```ruby
323
+ expect(described_class.new.decode_argv).to eq(["repo.create", {visibility: "private", upstream: "git@github.com:owner/project.git"}])
324
+ ```
325
+
326
+ Cover long options and a second example for `-v public -u https://github.com/owner/project.git` so both aliases and value parsing are exercised.
327
+
328
+ - [ ] **Step 2: Run the decoder specs and verify failure**
329
+
330
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb`
331
+
332
+ Expected: decoding fails because `repo.create` is not registered.
333
+
334
+ - [ ] **Step 3: Register the command and options**
335
+
336
+ Add `REPO_CREATE_COMMAND = T.let("repo.create", String)`, define:
337
+
338
+ ```ruby
339
+ REPO_CREATE_OPTIONS = T.let([
340
+ ["-v", "--visibility private|public", "Repository visibility"],
341
+ ["-u", "--upstream address", "Create a fork of this repository"],
342
+ long_help: "Create a GitHub repository from the current directory.",
343
+ ], T::Array[T.any(T::Hash[T.untyped, T.untyped], T::Array[String])])
344
+ ```
345
+
346
+ and register `"create" => REPO_CREATE_OPTIONS` under `"repo"`.
347
+
348
+ - [ ] **Step 4: Run the decoder specs**
349
+
350
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb`
351
+
352
+ Expected: 2 examples, 0 failures.
353
+
354
+ - [ ] **Step 5: Write a failing launcher dispatch spec**
355
+
356
+ Change the final line of `bin/geet` to `GeetLauncher.new.launch if $PROGRAM_NAME == __FILE__` so the class can be required safely. In `spec/unit/geet_launcher_spec.rb`, require `../../bin/geet`, stub `Commandline::Configuration#decode_argv` to return `[REPO_CREATE_COMMAND, {visibility: "private"}]`, expect `Services::CreateRepo.new.execute(visibility: "private")`, and expect `Git::Repository` not to receive `new`. This proves creation bypasses the existing-origin abstraction.
357
+
358
+ - [ ] **Step 6: Run the launcher spec and verify dispatch failure**
359
+
360
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/geet_launcher_spec.rb`
361
+
362
+ Expected: failure because the launcher does not handle `REPO_CREATE_COMMAND`.
363
+
364
+ - [ ] **Step 7: Add early launcher dispatch**
365
+
366
+ Immediately after decoding arguments, add:
367
+
368
+ ```ruby
369
+ if command == REPO_CREATE_COMMAND
370
+ Services::CreateRepo.new.execute(**options)
371
+ return
372
+ end
373
+ ```
374
+
375
+ Leave existing commands and repository construction unchanged below this early return.
376
+
377
+ - [ ] **Step 8: Document repository creation**
378
+
379
+ Add repository creation and forking to the supported operations. Add concise samples showing interactive visibility, `--visibility private`, and `--upstream git@github.com:owner/project.git`, plus the effects: SSH `origin`, preserved `upstream`, pushed current branch, and default-branch update.
380
+
381
+ - [ ] **Step 9: Run CLI, type, style, and full test verification**
382
+
383
+ Run: `GH_TOKEN=test bundle exec rspec spec/unit/commandline/configuration_spec.rb spec/unit/geet_launcher_spec.rb`
384
+
385
+ Expected: all examples pass.
386
+
387
+ Run: `GH_TOKEN=test bundle exec rake`
388
+
389
+ Expected: all examples pass.
390
+
391
+ Run: `bundle exec srb tc`
392
+
393
+ Expected: `No errors! Great job.`
394
+
395
+ Run: `bundle exec rubocop`
396
+
397
+ Expected: no offenses.
398
+
399
+ Run: `git diff --check`
400
+
401
+ Expected: no output and exit status 0.
402
+
403
+ - [ ] **Step 10: Commit CLI integration and documentation**
404
+
405
+ ```bash
406
+ git add lib/geet/commandline/commands.rb lib/geet/commandline/configuration.rb bin/geet spec/unit/commandline/configuration_spec.rb spec/unit/geet_launcher_spec.rb README.md
407
+ git commit -m "Expose repository creation command"
408
+ ```
409
+
410
+ ### Task 5: Final verification
411
+
412
+ **Files:**
413
+ - Verify only; modify files only to correct failures caused by this feature.
414
+
415
+ **Interfaces:**
416
+ - Consumes: all previous tasks.
417
+ - Produces: a clean, verified `repo-create` branch.
418
+
419
+ - [ ] **Step 1: Run the complete verification suite from a clean shell command**
420
+
421
+ ```bash
422
+ GH_TOKEN=test bundle exec rake
423
+ bundle exec srb tc
424
+ bundle exec rubocop
425
+ git diff --check
426
+ git status --short --branch
427
+ ```
428
+
429
+ Expected: all tests pass, Sorbet reports no errors, RuboCop reports no offenses, diff check is silent, and status shows `## repo-create` with no changed files.
430
+
431
+ - [ ] **Step 2: Review the commit series**
432
+
433
+ Run: `git log --oneline master..HEAD`
434
+
435
+ Expected: the design commit followed by conceptually atomic API, Git primitive, service, and CLI commits.