carson 4.0.2 → 4.0.3
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/RELEASE.md +12 -2
- data/VERSION +1 -1
- data/lib/carson/courier.rb +17 -8
- data/lib/carson/delivery.rb +5 -1
- data/lib/carson/runtime/deliver.rb +3 -3
- data/lib/carson/runtime/local/hooks.rb +7 -0
- data/lib/carson/runtime/local/onboard.rb +17 -1
- data/lib/carson/runtime/receive.rb +16 -0
- data/lib/carson/waybill.rb +1 -1
- data/lib/carson.rb +6 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5678a9b3d25a0a0c6e2b37182e53607b8c41e0a199771bbae7535ec776f71c8
|
|
4
|
+
data.tar.gz: c744e380475f851bdc142dc13693f3b35a229b93a45fdbc2ecb70e33fd3ba8cc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 136aeaa5ebf42de0f676875a44d8d4bf2eb758b9d70753718ba6cb27ca37756ca42af3518247e1a357c6e5a3bb8cdaa9bbdeff2d1fa9f3fd63302263b4d3c9a1
|
|
7
|
+
data.tar.gz: db5026a393f292c7dee8e1a6b3d63ec4463bcaebea179161ceb184271826d0c1a803bfc9a6a31f6cc5d7446f10be3e52739bebfbb70c548a8cb607fd1f57e4ae
|
data/RELEASE.md
CHANGED
|
@@ -11,6 +11,15 @@ Release-note scope rule:
|
|
|
11
11
|
|
|
12
12
|
- CLI grammar is now two-tier: portfolio commands (`list`, `onboard`, `offboard`, `refresh`, `version`) and repo-scoped commands (`carson <repo> <command>` or `carson <command>` from CWD)
|
|
13
13
|
|
|
14
|
+
## 4.0.3
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **No-checks repos can now merge** (#465) — `Waybill#cleared?` required CI to pass, but repos with no CI checks returned `:none`. These repos were permanently held. Now `:none` is treated as non-blocking.
|
|
19
|
+
- **Sync failure reported explicitly** — When local main fails to sync after a merge, `carson deliver` now prints "Local main not synced — run carson sync." instead of silently omitting the line.
|
|
20
|
+
- **Freshness fetch failure blocks delivery** — `Courier#deliver` ignored the return value of `fetch_latest`. If the fetch failed (network error), Carson reasoned from stale refs. Now blocks immediately with "cannot verify freshness — fetch failed" and Carson-first recovery.
|
|
21
|
+
- **PR data reaches the ledger** — The Courier's `record` method hardcoded `pr_number: nil` and `pr_url: nil`. Filed deliveries had no PR reference in the ledger. Now passes waybill tracking number and URL.
|
|
22
|
+
|
|
14
23
|
## 4.0.2
|
|
15
24
|
|
|
16
25
|
### Fixed
|
|
@@ -42,7 +51,7 @@ Release-note scope rule:
|
|
|
42
51
|
|
|
43
52
|
- **`carson deliver` waits for CI and merges automatically** — `carson deliver` now polls GitHub up to 6 times (default 30s interval) instead of checking once and returning immediately. PRs that pass CI are merged without re-running `carson deliver`. Agents that relied on instant return should expect it to block for up to 3 minutes.
|
|
44
53
|
- **Hold reasons renamed** — JSON output field `hold_reason` values changed: `inspector_pending` → `pending_at_registry`, `inspector_failed` → `failed_at_registry`, `inspector_error` → `error_at_registry`. Agents parsing these values must update.
|
|
45
|
-
- **Hold messages include recovery commands** — Human output for held deliveries now shows actionable next steps (e.g. `→
|
|
54
|
+
- **Hold messages include recovery commands** — Human output for held deliveries now shows actionable next steps (e.g. `→ carson deliver`) instead of prose descriptions.
|
|
46
55
|
|
|
47
56
|
### New
|
|
48
57
|
|
|
@@ -71,7 +80,8 @@ Release-note scope rule:
|
|
|
71
80
|
|
|
72
81
|
### What changed
|
|
73
82
|
|
|
74
|
-
- **Recovery hints re-enter through Carson, not blocked raw commands** — Delivery error recovery messages no longer suggest raw `gh pr create
|
|
83
|
+
- **Recovery hints re-enter through Carson, not blocked raw commands** — Delivery error recovery messages no longer suggest raw `gh pr create` or `gh pr merge`. All recovery paths guide the user back through Carson.
|
|
84
|
+
- **Auto-rebase on delivery** — `carson deliver` automatically rebases the branch when it's behind remote main. Only blocks if the rebase hits a conflict. No more manual `git rebase` before delivery.
|
|
75
85
|
- **Fast PR indentation guard restored** — The Ruby indentation guard in CI now correctly handles access modifier detection, fixing false positives that blocked PRs.
|
|
76
86
|
|
|
77
87
|
### No migration required
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.0.
|
|
1
|
+
4.0.3
|
data/lib/carson/courier.rb
CHANGED
|
@@ -125,12 +125,21 @@ module Carson
|
|
|
125
125
|
parcel = Parcel.new( label: parcel.label, head: @warehouse.current_head, shelf: parcel.shelf )
|
|
126
126
|
|
|
127
127
|
# 02. Parcel behind standard — not based on client's latest standard.
|
|
128
|
-
|
|
128
|
+
# The courier rebases automatically. Only blocks on conflict.
|
|
129
|
+
unless @warehouse.fetch_latest( registry: @warehouse.main_label )
|
|
130
|
+
return blocked( result,
|
|
131
|
+
"cannot verify freshness — fetch failed",
|
|
132
|
+
recovery: "carson sync, then carson deliver" )
|
|
133
|
+
end
|
|
129
134
|
unless @warehouse.based_on_latest_standard?( parcel )
|
|
130
135
|
remote_main = "#{@warehouse.bureau_address}/#{@warehouse.main_label}"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
say "Branch is behind #{remote_main} — rebasing..."
|
|
137
|
+
unless @warehouse.rebase_on_latest_standard!
|
|
138
|
+
return blocked( result,
|
|
139
|
+
"rebase conflict onto #{remote_main}",
|
|
140
|
+
recovery: "resolve conflicts, then carson deliver" )
|
|
141
|
+
end
|
|
142
|
+
parcel = Parcel.new( label: parcel.label, head: @warehouse.current_head, shelf: parcel.shelf )
|
|
134
143
|
end
|
|
135
144
|
|
|
136
145
|
# Announce the delivery.
|
|
@@ -172,7 +181,7 @@ module Carson
|
|
|
172
181
|
@warehouse.unseal_shelf! if outcome == "delivered" || outcome == "held" || outcome == "rejected"
|
|
173
182
|
|
|
174
183
|
# Update the ledger with the final outcome.
|
|
175
|
-
record( parcel, status: outcome || "filed", summary: result[ :hold_reason ] )
|
|
184
|
+
record( parcel, status: outcome || "filed", summary: result[ :hold_reason ], waybill: waybill )
|
|
176
185
|
|
|
177
186
|
result[ :exit ] ||= OK
|
|
178
187
|
result
|
|
@@ -254,7 +263,7 @@ module Carson
|
|
|
254
263
|
|
|
255
264
|
# Record a delivery state change in the ledger.
|
|
256
265
|
# No-op when no ledger is injected (e.g. tests).
|
|
257
|
-
def record( parcel, status:, summary: nil )
|
|
266
|
+
def record( parcel, status:, summary: nil, waybill: nil )
|
|
258
267
|
return unless @ledger
|
|
259
268
|
|
|
260
269
|
# The ledger needs a repository-like object with .path pointing
|
|
@@ -265,8 +274,8 @@ module Carson
|
|
|
265
274
|
branch_name: parcel.label,
|
|
266
275
|
head: parcel.head,
|
|
267
276
|
worktree_path: @warehouse.path,
|
|
268
|
-
pr_number:
|
|
269
|
-
pr_url:
|
|
277
|
+
pr_number: waybill&.tracking_number,
|
|
278
|
+
pr_url: waybill&.url,
|
|
270
279
|
status: status,
|
|
271
280
|
summary: summary,
|
|
272
281
|
cause: nil
|
data/lib/carson/delivery.rb
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Passive ledger record for one branch delivery attempt.
|
|
2
2
|
module Carson
|
|
3
3
|
class Delivery
|
|
4
|
-
ACTIVE_STATES = %w[preparing gated queued integrating escalated].freeze
|
|
4
|
+
ACTIVE_STATES = %w[preparing gated queued integrating escalated filed].freeze
|
|
5
5
|
BLOCKED_STATES = %w[gated escalated].freeze
|
|
6
6
|
READY_STATES = %w[queued].freeze
|
|
7
7
|
TERMINAL_STATES = %w[integrated failed superseded].freeze
|
|
@@ -60,6 +60,10 @@ module Carson
|
|
|
60
60
|
READY_STATES.include?( status )
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
def filed?
|
|
64
|
+
status == "filed"
|
|
65
|
+
end
|
|
66
|
+
|
|
63
67
|
def integrated?
|
|
64
68
|
status == "integrated"
|
|
65
69
|
end
|
|
@@ -231,7 +231,7 @@ module Carson
|
|
|
231
231
|
when :blocked
|
|
232
232
|
result[ :outcome ] = "blocked"
|
|
233
233
|
result[ :waited_seconds ] = elapsed_settle_seconds( started_at: started_at )
|
|
234
|
-
result[ :recovery ] = "
|
|
234
|
+
result[ :recovery ] = "carson deliver" if evaluation[ :cause ] == "freshness"
|
|
235
235
|
apply_handoff!(
|
|
236
236
|
result: result,
|
|
237
237
|
reason: evaluation.fetch( :reason ),
|
|
@@ -752,7 +752,7 @@ module Carson
|
|
|
752
752
|
|
|
753
753
|
def freshness_recovery( freshness: )
|
|
754
754
|
remote_ref = freshness.fetch( :remote_ref )
|
|
755
|
-
return "
|
|
755
|
+
return "carson deliver" if freshness.fetch( :status ) == :behind
|
|
756
756
|
|
|
757
757
|
"carson deliver (once #{remote_ref} is reachable)"
|
|
758
758
|
end
|
|
@@ -840,7 +840,7 @@ module Carson
|
|
|
840
840
|
reason: "freshness_behind",
|
|
841
841
|
cause: "freshness",
|
|
842
842
|
summary: "branch is behind #{remote_main}",
|
|
843
|
-
recovery: "
|
|
843
|
+
recovery: "carson deliver"
|
|
844
844
|
} if merge_state == "BEHIND"
|
|
845
845
|
|
|
846
846
|
return {
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
module Carson
|
|
3
3
|
class Runtime
|
|
4
4
|
module Local
|
|
5
|
+
# Refreshes hooks only — safe to run regardless of worktree or
|
|
6
|
+
# uncommitted-changes state, because hooks write to ~/.carson/hooks/
|
|
7
|
+
# and only touch .git/config, not the working tree.
|
|
8
|
+
def refresh_hooks!
|
|
9
|
+
prepare!
|
|
10
|
+
end
|
|
11
|
+
|
|
5
12
|
private
|
|
6
13
|
|
|
7
14
|
# Installs required hook files and enforces repository hook path.
|
|
@@ -120,8 +120,14 @@ module Carson
|
|
|
120
120
|
|
|
121
121
|
safety = portfolio_repo_safety( repo_path: repo_path )
|
|
122
122
|
unless safety.fetch( :safe )
|
|
123
|
+
# Hooks write to ~/.carson/hooks/, not the repo — always safe to refresh.
|
|
124
|
+
hooks_status = refresh_hooks_single_repo( repo_path: repo_path )
|
|
123
125
|
reason = safety.fetch( :reasons ).join( ", " )
|
|
124
|
-
|
|
126
|
+
if hooks_status == EXIT_OK
|
|
127
|
+
puts_line "#{repo_name}: hooks refreshed, templates pending (#{reason})"
|
|
128
|
+
else
|
|
129
|
+
puts_line "#{repo_name}: PENDING (#{reason})"
|
|
130
|
+
end
|
|
125
131
|
record_batch_skip( command: "refresh", repo_path: repo_path, reason: reason )
|
|
126
132
|
pending += 1
|
|
127
133
|
next
|
|
@@ -304,6 +310,16 @@ module Carson
|
|
|
304
310
|
EXIT_ERROR
|
|
305
311
|
end
|
|
306
312
|
|
|
313
|
+
# Refreshes hooks only for a governed repo using a scoped Runtime.
|
|
314
|
+
# Used when the full refresh is blocked by active worktrees or uncommitted
|
|
315
|
+
# changes — hooks write to ~/.carson/hooks/ and do not touch the working tree.
|
|
316
|
+
def refresh_hooks_single_repo( repo_path: )
|
|
317
|
+
scoped_runtime = build_scoped_runtime( repo_path: repo_path )
|
|
318
|
+
scoped_runtime.refresh_hooks!
|
|
319
|
+
rescue StandardError
|
|
320
|
+
EXIT_ERROR
|
|
321
|
+
end
|
|
322
|
+
|
|
307
323
|
def refresh_status_label( status: )
|
|
308
324
|
case status
|
|
309
325
|
when EXIT_OK then "OK"
|
|
@@ -74,7 +74,23 @@ module Carson
|
|
|
74
74
|
|
|
75
75
|
puts_line "#{repository.name}: #{deliveries.length} active deliver#{deliveries.length == 1 ? 'y' : 'ies'}" unless silent
|
|
76
76
|
|
|
77
|
+
# Collect worktree paths of filed deliveries before reconciliation.
|
|
78
|
+
# Receive takes over lifecycle management — the courier's polling window is over.
|
|
79
|
+
filed_worktree_paths = deliveries
|
|
80
|
+
.select( &:filed? )
|
|
81
|
+
.map( &:worktree_path )
|
|
82
|
+
.compact
|
|
83
|
+
.reject { |path| path.to_s.strip.empty? }
|
|
84
|
+
|
|
77
85
|
reconciled = deliveries.map { |item| scoped_runtime.send( :reconcile_delivery!, delivery: item ) }
|
|
86
|
+
|
|
87
|
+
# Unseal worktrees that were filed — receive now owns the delivery lifecycle.
|
|
88
|
+
unless dry_run
|
|
89
|
+
filed_worktree_paths.each do |worktree_path|
|
|
90
|
+
Warehouse.new( path: worktree_path ).unseal_shelf!
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
78
94
|
next_to_integrate = reconciled.find( &:ready? )&.key
|
|
79
95
|
|
|
80
96
|
reconciled.each do |delivery|
|
data/lib/carson/waybill.rb
CHANGED
|
@@ -98,7 +98,7 @@ module Carson
|
|
|
98
98
|
def cleared?
|
|
99
99
|
return false unless filed?
|
|
100
100
|
return false if draft?
|
|
101
|
-
return false unless @ci == :pass
|
|
101
|
+
return false unless @ci == :pass || @ci == :none
|
|
102
102
|
return false if merge_conflicting? || merge_behind? || merge_policy_blocked?
|
|
103
103
|
merge_status = @state&.dig( "mergeStateStatus" ).to_s.upcase
|
|
104
104
|
mergeable = @state&.dig( "mergeable" ).to_s.upcase
|
data/lib/carson.rb
CHANGED
|
@@ -34,7 +34,11 @@ module Carson
|
|
|
34
34
|
case result[ :outcome ]
|
|
35
35
|
when "delivered"
|
|
36
36
|
output.puts "#{BADGE} Merged."
|
|
37
|
-
|
|
37
|
+
if result[ :synced ]
|
|
38
|
+
output.puts "#{BADGE} Local main synced."
|
|
39
|
+
elsif result.key?( :synced )
|
|
40
|
+
output.puts "#{BADGE} Local main not synced \u2014 run carson sync."
|
|
41
|
+
end
|
|
38
42
|
when "held"
|
|
39
43
|
diagnosis, *recovery_steps = translate_hold( result[ :hold_reason ], remote_main: remote_main )
|
|
40
44
|
output.puts "#{BADGE} #{diagnosis}"
|
|
@@ -64,7 +68,7 @@ module Carson
|
|
|
64
68
|
when "merge_conflict"
|
|
65
69
|
[ "Merge conflict with #{remote_main}.", "git rebase #{remote_main}", "carson deliver" ]
|
|
66
70
|
when "behind_registry"
|
|
67
|
-
[ "Branch is behind #{remote_main}.", "
|
|
71
|
+
[ "Branch is behind #{remote_main}.", "carson deliver" ]
|
|
68
72
|
when "policy_block"
|
|
69
73
|
[ "Blocked by branch protection rules." ]
|
|
70
74
|
when "mergeability_pending"
|