carson 3.22.1 → 3.23.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.
- checksums.yaml +4 -4
- data/API.md +15 -11
- data/MANUAL.md +32 -38
- data/README.md +6 -9
- data/RELEASE.md +23 -0
- data/VERSION +1 -1
- data/carson.gemspec +1 -0
- data/lib/carson/adapters/agent.rb +2 -2
- data/lib/carson/branch.rb +38 -0
- data/lib/carson/cli.rb +13 -14
- data/lib/carson/config.rb +34 -18
- data/lib/carson/delivery.rb +64 -0
- data/lib/carson/ledger.rb +305 -0
- data/lib/carson/repository.rb +47 -0
- data/lib/carson/revision.rb +30 -0
- data/lib/carson/runtime/audit.rb +6 -6
- data/lib/carson/runtime/deliver.rb +112 -169
- data/lib/carson/runtime/govern.rb +213 -399
- data/lib/carson/runtime/housekeep.rb +4 -4
- data/lib/carson/runtime/local/onboard.rb +5 -5
- data/lib/carson/runtime/local/prune.rb +4 -4
- data/lib/carson/runtime/local/template.rb +4 -4
- data/lib/carson/runtime/review/gate_support.rb +14 -12
- data/lib/carson/runtime/review/sweep_support.rb +2 -2
- data/lib/carson/runtime/review/utility.rb +1 -1
- data/lib/carson/runtime/setup.rb +10 -27
- data/lib/carson/runtime/status.rb +87 -226
- data/lib/carson/runtime.rb +25 -2
- data/lib/carson/worktree.rb +5 -5
- data/lib/carson.rb +5 -0
- metadata +27 -2
|
@@ -1,120 +1,118 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# `carson deliver` pushes and creates the PR.
|
|
4
|
-
# `carson deliver --merge` also merges if CI passes or no checks are configured.
|
|
5
|
-
# `carson deliver --json` outputs structured result for agent consumption.
|
|
1
|
+
# Branch delivery lifecycle — push, create/update PR, and register Carson-owned delivery state.
|
|
2
|
+
# `carson deliver` is now the async handoff point. It does not wait for merge.
|
|
6
3
|
module Carson
|
|
7
4
|
class Runtime
|
|
8
5
|
module Deliver
|
|
9
6
|
# Entry point for `carson deliver`.
|
|
10
|
-
# Pushes current branch,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if branch == main
|
|
20
|
-
result[ :error ] = "cannot deliver from #{main}"
|
|
7
|
+
# Pushes the current branch, ensures a PR exists, records delivery state, and returns.
|
|
8
|
+
def deliver!( title: nil, body_file: nil, json_output: false )
|
|
9
|
+
branch_name = current_branch
|
|
10
|
+
main_branch = config.main_branch
|
|
11
|
+
remote_name = config.git_remote
|
|
12
|
+
result = { command: "deliver", branch: branch_name }
|
|
13
|
+
|
|
14
|
+
if branch_name == main_branch
|
|
15
|
+
result[ :error ] = "cannot deliver from #{main_branch}"
|
|
21
16
|
result[ :recovery ] = "carson worktree create <name>"
|
|
22
17
|
return deliver_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
23
18
|
end
|
|
24
19
|
|
|
25
|
-
|
|
26
|
-
# `push_prep: true` stages and commits managed drift so the subsequent
|
|
27
|
-
# push carries the canonical content even though deliver uses --no-verify.
|
|
28
|
-
# Output is captured to prevent pollution of --json mode.
|
|
29
|
-
# Diagnostics are preserved for error reporting.
|
|
30
|
-
sync_exit, sync_diagnostics = begin
|
|
31
|
-
saved_output, saved_error = @output, @error
|
|
32
|
-
captured_out = StringIO.new
|
|
33
|
-
captured_err = StringIO.new
|
|
34
|
-
@output = captured_out
|
|
35
|
-
@error = captured_err
|
|
36
|
-
exit_code = template_apply!( push_prep: true )
|
|
37
|
-
[ exit_code, captured_out.string + captured_err.string ]
|
|
38
|
-
rescue StandardError => exception
|
|
39
|
-
[ EXIT_ERROR, "template sync error: #{exception.message}" ]
|
|
40
|
-
ensure
|
|
41
|
-
@output, @error = saved_output, saved_error
|
|
42
|
-
end
|
|
43
|
-
|
|
20
|
+
sync_exit, sync_diagnostics = deliver_template_sync
|
|
44
21
|
if sync_exit == EXIT_ERROR
|
|
45
22
|
result[ :error ] = sync_diagnostics.to_s.strip.empty? ? "template sync failed" : sync_diagnostics.strip
|
|
46
23
|
return deliver_finish( result: result, exit_code: sync_exit, json_output: json_output )
|
|
47
24
|
end
|
|
48
25
|
|
|
49
|
-
|
|
50
|
-
push_exit = push_branch!( branch: branch, remote: remote, result: result )
|
|
26
|
+
push_exit = push_branch!( branch: branch_name, remote: remote_name, result: result )
|
|
51
27
|
return deliver_finish( result: result, exit_code: push_exit, json_output: json_output ) unless push_exit == EXIT_OK
|
|
52
28
|
|
|
53
|
-
# Step 3: find or create the PR.
|
|
54
29
|
pr_number, pr_url = find_or_create_pr!(
|
|
55
|
-
branch:
|
|
30
|
+
branch: branch_name,
|
|
31
|
+
title: title,
|
|
32
|
+
body_file: body_file,
|
|
33
|
+
result: result
|
|
56
34
|
)
|
|
57
|
-
if pr_number.nil?
|
|
58
|
-
|
|
59
|
-
|
|
35
|
+
return deliver_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output ) if pr_number.nil?
|
|
36
|
+
|
|
37
|
+
branch = branch_record( name: branch_name )
|
|
38
|
+
delivery = ledger.upsert_delivery(
|
|
39
|
+
repository: repository_record,
|
|
40
|
+
branch_name: branch.name,
|
|
41
|
+
head: branch.head || current_head,
|
|
42
|
+
worktree_path: branch.worktree || repo_root,
|
|
43
|
+
authority: config.govern_authority,
|
|
44
|
+
pr_number: pr_number,
|
|
45
|
+
pr_url: pr_url,
|
|
46
|
+
status: "preparing",
|
|
47
|
+
summary: "delivery accepted",
|
|
48
|
+
cause: nil
|
|
49
|
+
)
|
|
50
|
+
delivery = assess_delivery!( delivery: delivery, branch_name: branch.name )
|
|
60
51
|
|
|
61
52
|
result[ :pr_number ] = pr_number
|
|
62
53
|
result[ :pr_url ] = pr_url
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
# Step 4: check CI status.
|
|
69
|
-
ci_status = check_pr_ci( number: pr_number )
|
|
70
|
-
result[ :ci ] = ci_status.to_s
|
|
71
|
-
|
|
72
|
-
case ci_status
|
|
73
|
-
when :pass, :none
|
|
74
|
-
# Continue to review gate. :none means no checks configured — nothing to wait for.
|
|
75
|
-
when :pending
|
|
76
|
-
result[ :recovery ] = "gh pr checks #{pr_number} --watch && carson deliver --merge"
|
|
77
|
-
return deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
78
|
-
when :fail
|
|
79
|
-
result[ :recovery ] = "gh pr checks #{pr_number} — fix failures, push, then `carson deliver --merge`"
|
|
80
|
-
return deliver_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
81
|
-
end
|
|
54
|
+
result[ :ci ] = check_pr_ci( number: pr_number ).to_s
|
|
55
|
+
result[ :delivery ] = delivery_payload( delivery: delivery )
|
|
56
|
+
result[ :summary ] = delivery.summary
|
|
57
|
+
result[ :next_step ] = "carson status"
|
|
82
58
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
result[ :review ] = review.fetch( :review ).to_s
|
|
86
|
-
if review.fetch( :review ) == :changes_requested
|
|
87
|
-
result[ :error ] = "review changes requested on PR ##{pr_number}"
|
|
88
|
-
result[ :recovery ] = "address review comments, push, then `carson deliver --merge`"
|
|
89
|
-
return deliver_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
90
|
-
end
|
|
91
|
-
if review.fetch( :status ) == :fail
|
|
92
|
-
result[ :error ] = "review gate blocked on PR ##{pr_number}: #{review.fetch( :detail )}"
|
|
93
|
-
result[ :recovery ] = "resolve review gate blockers, push, then `carson deliver --merge`"
|
|
94
|
-
return deliver_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
|
|
95
|
-
end
|
|
96
|
-
if review.fetch( :status ) == :error
|
|
97
|
-
result[ :error ] = "unable to evaluate review gate for PR ##{pr_number}: #{review.fetch( :detail )}"
|
|
98
|
-
result[ :recovery ] = "run `carson review gate`, then retry `carson deliver --merge`"
|
|
99
|
-
return deliver_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
|
|
100
|
-
end
|
|
59
|
+
deliver_finish( result: result, exit_code: EXIT_OK, json_output: json_output )
|
|
60
|
+
end
|
|
101
61
|
|
|
102
|
-
|
|
103
|
-
merge_exit = merge_pr!( number: pr_number, result: result )
|
|
104
|
-
return deliver_finish( result: result, exit_code: merge_exit, json_output: json_output ) unless merge_exit == EXIT_OK
|
|
62
|
+
private
|
|
105
63
|
|
|
106
|
-
|
|
64
|
+
def deliver_template_sync
|
|
65
|
+
saved_output, saved_error = @output, @error
|
|
66
|
+
captured_out = StringIO.new
|
|
67
|
+
captured_err = StringIO.new
|
|
68
|
+
@output = captured_out
|
|
69
|
+
@error = captured_err
|
|
70
|
+
exit_code = template_apply!( push_prep: true )
|
|
71
|
+
[ exit_code, captured_out.string + captured_err.string ]
|
|
72
|
+
rescue StandardError => exception
|
|
73
|
+
[ EXIT_ERROR, "template sync error: #{exception.message}" ]
|
|
74
|
+
ensure
|
|
75
|
+
@output, @error = saved_output, saved_error
|
|
76
|
+
end
|
|
107
77
|
|
|
108
|
-
|
|
109
|
-
|
|
78
|
+
# Assesses delivery readiness and records Carson's current branch state.
|
|
79
|
+
def assess_delivery!( delivery:, branch_name: )
|
|
80
|
+
review = check_pr_review( number: delivery.pull_request_number, branch: branch_name, pr_url: delivery.pull_request_url )
|
|
81
|
+
ci = check_pr_ci( number: delivery.pull_request_number )
|
|
82
|
+
status, cause, summary = delivery_assessment( ci: ci, review: review )
|
|
83
|
+
|
|
84
|
+
ledger.update_delivery(
|
|
85
|
+
delivery: delivery,
|
|
86
|
+
status: status,
|
|
87
|
+
cause: cause,
|
|
88
|
+
summary: summary,
|
|
89
|
+
pr_number: delivery.pull_request_number,
|
|
90
|
+
pr_url: delivery.pull_request_url,
|
|
91
|
+
worktree_path: delivery.worktree_path
|
|
92
|
+
)
|
|
93
|
+
end
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
95
|
+
def delivery_assessment( ci:, review: )
|
|
96
|
+
return [ "gated", "ci", "waiting for CI checks" ] if ci == :pending
|
|
97
|
+
return [ "gated", "ci", "CI checks are failing" ] if ci == :fail
|
|
98
|
+
return [ "gated", "review", "review changes requested" ] if review.fetch( :review, :none ) == :changes_requested
|
|
99
|
+
return [ "gated", "review", "waiting for review" ] if review.fetch( :review, :none ) == :review_required
|
|
100
|
+
return [ "gated", "review", review.fetch( :detail ).to_s ] if review.fetch( :status, :pass ) == :fail
|
|
101
|
+
return [ "gated", "policy", "unable to assess review gate: #{review.fetch( :detail )}" ] if review.fetch( :status, :pass ) == :error
|
|
113
102
|
|
|
114
|
-
|
|
103
|
+
[ "queued", nil, "ready to integrate into #{config.main_branch}" ]
|
|
115
104
|
end
|
|
116
105
|
|
|
117
|
-
|
|
106
|
+
def delivery_payload( delivery: )
|
|
107
|
+
{
|
|
108
|
+
id: delivery.id,
|
|
109
|
+
status: delivery.status,
|
|
110
|
+
head: delivery.head,
|
|
111
|
+
worktree_path: delivery.worktree_path,
|
|
112
|
+
revision_count: delivery.revision_count,
|
|
113
|
+
cause: delivery.cause
|
|
114
|
+
}
|
|
115
|
+
end
|
|
118
116
|
|
|
119
117
|
# Outputs the final result — JSON or human-readable — and returns exit code.
|
|
120
118
|
def deliver_finish( result:, exit_code:, json_output: )
|
|
@@ -131,47 +129,21 @@ module Carson
|
|
|
131
129
|
|
|
132
130
|
# Human-readable output for deliver results.
|
|
133
131
|
def print_deliver_human( result: )
|
|
134
|
-
exit_code = result.fetch( :exit_code )
|
|
135
|
-
|
|
136
132
|
if result[ :error ]
|
|
137
133
|
puts_line result[ :error ]
|
|
138
134
|
puts_line " → #{result[ :recovery ]}" if result[ :recovery ]
|
|
139
135
|
return
|
|
140
136
|
end
|
|
141
137
|
|
|
142
|
-
if result[ :pr_number ]
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if result[ :ci ]
|
|
147
|
-
ci = result[ :ci ]
|
|
148
|
-
case ci
|
|
149
|
-
when "pass"
|
|
150
|
-
puts_line "CI: pass"
|
|
151
|
-
when "none"
|
|
152
|
-
puts_line "CI: none — no checks configured, proceeding."
|
|
153
|
-
when "pending"
|
|
154
|
-
puts_line "CI: pending — merge when checks complete."
|
|
155
|
-
puts_line " → #{result[ :recovery ]}" if result[ :recovery ]
|
|
156
|
-
when "fail"
|
|
157
|
-
puts_line "CI: not passing yet — fix before merging."
|
|
158
|
-
puts_line " → #{result[ :recovery ]}" if result[ :recovery ]
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
if result[ :merged ]
|
|
163
|
-
puts_line "Merged PR ##{result[ :pr_number ]} via #{result[ :merge_method ]}."
|
|
164
|
-
puts_line " Next: #{result[ :next_step ]}" if result[ :next_step ]
|
|
138
|
+
puts_line "PR: ##{result[ :pr_number ]} #{result[ :pr_url ]}" if result[ :pr_number ]
|
|
139
|
+
if result[ :delivery ]
|
|
140
|
+
puts_line "Delivery: #{result.dig( :delivery, :status )}"
|
|
165
141
|
end
|
|
142
|
+
puts_line "Summary: #{result[ :summary ]}" if result[ :summary ]
|
|
143
|
+
puts_line " Next: #{result[ :next_step ]}" if result[ :next_step ]
|
|
166
144
|
end
|
|
167
145
|
|
|
168
146
|
# Pushes the branch to the remote with tracking.
|
|
169
|
-
# Uses --no-verify to skip the pre-push hook that Carson itself installed.
|
|
170
|
-
# The hook blocks raw pushes unconditionally; Carson bypasses by skipping it.
|
|
171
|
-
# Template sync (previously in the hook) now runs in deliver! before push.
|
|
172
|
-
# On non-fast-forward rejection (typically after rebase), retries with
|
|
173
|
-
# --force-with-lease — a protected force push that rejects if the remote
|
|
174
|
-
# ref has been updated by another actor since the last fetch.
|
|
175
147
|
def push_branch!( branch:, remote:, result: )
|
|
176
148
|
_, push_stderr, push_success, = git_run( "push", "--no-verify", "-u", remote, branch )
|
|
177
149
|
|
|
@@ -190,9 +162,6 @@ module Carson
|
|
|
190
162
|
end
|
|
191
163
|
|
|
192
164
|
# Retries push with --force-with-lease after a non-fast-forward rejection.
|
|
193
|
-
# The lease check compares the local tracking ref against the remote — if
|
|
194
|
-
# another actor pushed since our last fetch, the push is refused ("stale info").
|
|
195
|
-
# This is atomic and safe, unlike delete-and-re-push.
|
|
196
165
|
def force_push_with_lease!( branch:, remote:, result: )
|
|
197
166
|
puts_verbose "push rejected (non-fast-forward), retrying with --force-with-lease"
|
|
198
167
|
_, lease_stderr, lease_success, = git_run( "push", "--no-verify", "--force-with-lease", "-u", remote, branch )
|
|
@@ -202,7 +171,6 @@ module Carson
|
|
|
202
171
|
return EXIT_OK
|
|
203
172
|
end
|
|
204
173
|
|
|
205
|
-
# --force-with-lease rejected — another actor pushed to this branch.
|
|
206
174
|
if lease_stderr.to_s.include?( "stale info" )
|
|
207
175
|
result[ :error ] = "force-with-lease rejected — another push landed on #{branch} since your last fetch"
|
|
208
176
|
result[ :recovery ] = "git fetch #{remote} #{branch} && carson deliver"
|
|
@@ -215,21 +183,14 @@ module Carson
|
|
|
215
183
|
end
|
|
216
184
|
|
|
217
185
|
# Finds an existing PR for the branch, or creates a new one.
|
|
218
|
-
# Returns [number, url] or [nil, nil] on failure.
|
|
219
186
|
def find_or_create_pr!( branch:, title: nil, body_file: nil, result: )
|
|
220
|
-
# Check for existing PR.
|
|
221
187
|
existing = find_existing_pr( branch: branch )
|
|
222
188
|
return existing if existing.first
|
|
223
189
|
|
|
224
|
-
# Create a new PR.
|
|
225
190
|
create_pr!( branch: branch, title: title, body_file: body_file, result: result )
|
|
226
191
|
end
|
|
227
192
|
|
|
228
193
|
# Queries gh for an open PR on this branch.
|
|
229
|
-
# Returns [number, url] or [nil, nil].
|
|
230
|
-
# gh pr view returns any PR on the branch — open, merged, or closed.
|
|
231
|
-
# We check state explicitly so merged/closed PRs are treated as absent,
|
|
232
|
-
# letting find_or_create_pr! fall through to create a new PR.
|
|
233
194
|
def find_existing_pr( branch: )
|
|
234
195
|
stdout, _, success, = gh_run(
|
|
235
196
|
"pr", "view", branch,
|
|
@@ -245,11 +206,10 @@ module Carson
|
|
|
245
206
|
end
|
|
246
207
|
|
|
247
208
|
# Creates a PR via gh. Title defaults to branch name humanised.
|
|
248
|
-
# Returns [number, url] or [nil, nil] on failure.
|
|
249
209
|
def create_pr!( branch:, title: nil, body_file: nil, result: )
|
|
250
210
|
pr_title = title || default_pr_title( branch: branch )
|
|
251
|
-
|
|
252
211
|
args = [ "pr", "create", "--title", pr_title, "--head", branch ]
|
|
212
|
+
|
|
253
213
|
if body_file && File.exist?( body_file )
|
|
254
214
|
args.push( "--body-file", body_file )
|
|
255
215
|
else
|
|
@@ -265,24 +225,18 @@ module Carson
|
|
|
265
225
|
return [ nil, nil ]
|
|
266
226
|
end
|
|
267
227
|
|
|
268
|
-
# gh pr create prints the URL on success. Parse number from it.
|
|
269
228
|
pr_url = stdout.to_s.strip
|
|
270
229
|
pr_number = pr_url.split( "/" ).last.to_i
|
|
271
|
-
if pr_number > 0
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
# Fallback: query the just-created PR.
|
|
275
|
-
find_existing_pr( branch: branch )
|
|
276
|
-
end
|
|
230
|
+
return [ pr_number, pr_url ] if pr_number > 0
|
|
231
|
+
|
|
232
|
+
find_existing_pr( branch: branch )
|
|
277
233
|
end
|
|
278
234
|
|
|
279
|
-
# Generates a default PR title from the branch name.
|
|
280
235
|
def default_pr_title( branch: )
|
|
281
|
-
branch.tr( "-", " " ).gsub( "/", ": " ).sub( /\A\w/ ) {
|
|
236
|
+
branch.tr( "-", " " ).gsub( "/", ": " ).sub( /\A\w/ ) { |character| character.upcase }
|
|
282
237
|
end
|
|
283
238
|
|
|
284
239
|
# Checks CI status on a PR. Returns :pass, :fail, :pending, or :none.
|
|
285
|
-
# Uses the `bucket` field (pass/fail/pending) from `gh pr checks --json`.
|
|
286
240
|
def check_pr_ci( number: )
|
|
287
241
|
stdout, _, success, = gh_run(
|
|
288
242
|
"pr", "checks", number.to_s,
|
|
@@ -293,7 +247,7 @@ module Carson
|
|
|
293
247
|
checks = JSON.parse( stdout ) rescue []
|
|
294
248
|
return :none if checks.empty?
|
|
295
249
|
|
|
296
|
-
buckets = checks.map {
|
|
250
|
+
buckets = checks.map { |entry| entry[ "bucket" ].to_s.downcase }
|
|
297
251
|
return :fail if buckets.include?( "fail" )
|
|
298
252
|
return :pending if buckets.include?( "pending" )
|
|
299
253
|
|
|
@@ -320,10 +274,20 @@ module Carson
|
|
|
320
274
|
{ status: :error, review: :error, detail: exception.message }
|
|
321
275
|
end
|
|
322
276
|
|
|
323
|
-
#
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
277
|
+
# Returns the current PR state for govern reconciliation.
|
|
278
|
+
def pull_request_state( number: )
|
|
279
|
+
stdout, _, success, = gh_run(
|
|
280
|
+
"pr", "view", number.to_s,
|
|
281
|
+
"--json", "number,state,isDraft,url"
|
|
282
|
+
)
|
|
283
|
+
return nil unless success
|
|
284
|
+
|
|
285
|
+
JSON.parse( stdout )
|
|
286
|
+
rescue JSON::ParserError
|
|
287
|
+
nil
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Merges the PR using the governed merge method.
|
|
327
291
|
def merge_pr!( number:, result: )
|
|
328
292
|
method = config.govern_merge_method
|
|
329
293
|
result[ :merge_method ] = method
|
|
@@ -345,9 +309,6 @@ module Carson
|
|
|
345
309
|
end
|
|
346
310
|
|
|
347
311
|
# Syncs main after a successful merge.
|
|
348
|
-
# Pulls into the main worktree directly — does not attempt checkout,
|
|
349
|
-
# because checkout would fail when running inside a feature worktree
|
|
350
|
-
# (main is already checked output in the main tree).
|
|
351
312
|
def sync_after_merge!( remote:, main:, result: )
|
|
352
313
|
main_root = main_worktree_root
|
|
353
314
|
_, pull_stderr, pull_success, = Open3.capture3(
|
|
@@ -362,24 +323,6 @@ module Carson
|
|
|
362
323
|
puts_verbose "sync failed: #{pull_stderr.to_s.strip}"
|
|
363
324
|
end
|
|
364
325
|
end
|
|
365
|
-
|
|
366
|
-
# Builds next-step guidance after a successful merge.
|
|
367
|
-
# Detects whether the agent is inside a worktree and suggests cleanup.
|
|
368
|
-
def compute_post_merge_next_step!( result: )
|
|
369
|
-
main_root = main_worktree_root
|
|
370
|
-
current_wt = worktree_list
|
|
371
|
-
.reject { it.path == realpath_safe( main_root ) }
|
|
372
|
-
.find { it.holds_cwd? }
|
|
373
|
-
|
|
374
|
-
if current_wt
|
|
375
|
-
wt_name = File.basename( current_wt.path )
|
|
376
|
-
result[ :next_step ] = "cd #{main_root} && carson worktree remove #{wt_name}"
|
|
377
|
-
else
|
|
378
|
-
result[ :next_step ] = "carson prune"
|
|
379
|
-
end
|
|
380
|
-
rescue StandardError
|
|
381
|
-
# Best-effort — do not fail deliver because of next-step detection.
|
|
382
|
-
end
|
|
383
326
|
end
|
|
384
327
|
|
|
385
328
|
include Deliver
|