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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8bf071658d6db37302910554b54a528ac3efe7faa22a1cb88b42d13faf0b6b30
4
- data.tar.gz: 0e30d7ca60c8d8f13438562b003009a64820ff7109ca1c525a127282a5c942c9
3
+ metadata.gz: a5678a9b3d25a0a0c6e2b37182e53607b8c41e0a199771bbae7535ec776f71c8
4
+ data.tar.gz: c744e380475f851bdc142dc13693f3b35a229b93a45fdbc2ecb70e33fd3ba8cc
5
5
  SHA512:
6
- metadata.gz: 0d50b557c6e2a29b78d4f82dfedf2f89c4d6eacc68fb561d5684eb7c40582ba43de5ff84b6929c992fd98b5069e1ee15b17fb434f4512b3ea30f94c97a387ae6
7
- data.tar.gz: 4cc5c13c062c226ffcfd8627882ecb6c3093169e61dc759d03e2b77110f93323b77bc90761107a8e2a7696b211fe42c603ad0823639c3eb062ff66753410f96b
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. `→ git rebase origin/main` then `→ carson deliver`) instead of prose descriptions.
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`, `gh pr merge`, or `git rebase` commands. All recovery paths now guide the user back through Carson. Freshness blocks say "refresh this branch onto the target, then `carson deliver`" instead of teaching raw git recipes.
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.2
1
+ 4.0.3
@@ -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
- @warehouse.fetch_latest( registry: @warehouse.main_label )
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
- return blocked( result,
132
- "branch is behind #{remote_main}",
133
- recovery: "git rebase #{remote_main}, then carson deliver" )
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: nil,
269
- pr_url: nil,
277
+ pr_number: waybill&.tracking_number,
278
+ pr_url: waybill&.url,
270
279
  status: status,
271
280
  summary: summary,
272
281
  cause: nil
@@ -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 ] = "refresh this branch onto #{config.git_remote}/#{main}, then carson deliver" if evaluation[ :cause ] == "freshness"
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 "refresh this branch onto #{remote_ref}, then carson deliver" if freshness.fetch( :status ) == :behind
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: "refresh this branch onto #{remote_main}, then carson deliver"
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
- puts_line "#{repo_name}: PENDING (#{reason})"
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|
@@ -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
- output.puts "#{BADGE} Local main synced." if result[ :synced ]
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}.", "git rebase #{remote_main}", "carson deliver" ]
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"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.2
4
+ version: 4.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang