carson 3.30.1 → 3.30.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.
@@ -164,58 +164,6 @@ module Carson
164
164
  exit_code
165
165
  end
166
166
 
167
- # Runs audit across all governed repositories.
168
- def audit_all!
169
- repos = config.govern_repos
170
- if repos.empty?
171
- puts_line "No governed repositories configured."
172
- puts_line " Run carson onboard in each repo to register."
173
- return EXIT_ERROR
174
- end
175
-
176
- puts_line ""
177
- puts_line "Audit all (#{repos.length} repo#{plural_suffix( count: repos.length )})"
178
- passed = 0
179
- blocked = 0
180
- failed = 0
181
-
182
- repos.each do |repo_path|
183
- repo_name = File.basename( repo_path )
184
- unless Dir.exist?( repo_path )
185
- puts_line "#{repo_name}: not found"
186
- record_batch_skip( command: "audit", repo_path: repo_path, reason: "path not found" )
187
- failed += 1
188
- next
189
- end
190
-
191
- begin
192
- scoped_runtime = build_scoped_runtime( repo_path: repo_path )
193
- status = scoped_runtime.audit!
194
- case status
195
- when EXIT_OK
196
- puts_line "#{repo_name}: ok" unless verbose?
197
- clear_batch_success( command: "audit", repo_path: repo_path )
198
- passed += 1
199
- when EXIT_BLOCK
200
- puts_line "#{repo_name}: needs attention" unless verbose?
201
- blocked += 1
202
- else
203
- puts_line "#{repo_name}: could not complete" unless verbose?
204
- record_batch_skip( command: "audit", repo_path: repo_path, reason: "audit failed" )
205
- failed += 1
206
- end
207
- rescue StandardError => exception
208
- puts_line "#{repo_name}: could not complete (#{exception.message})"
209
- record_batch_skip( command: "audit", repo_path: repo_path, reason: exception.message )
210
- failed += 1
211
- end
212
- end
213
-
214
- puts_line ""
215
- puts_line "Audit all complete: #{passed} ok, #{blocked} blocked, #{failed} failed."
216
- blocked.zero? && failed.zero? ? EXIT_OK : EXIT_BLOCK
217
- end
218
-
219
167
  # rubocop:disable Layout/AccessModifierIndentation -- tab-width calculation produces unfixable mixed tabs+spaces
220
168
  private
221
169
  # rubocop:enable Layout/AccessModifierIndentation
@@ -288,7 +288,7 @@ module Carson
288
288
  when :blocked
289
289
  result[ :outcome ] = "blocked"
290
290
  result[ :waited_seconds ] = elapsed_settle_seconds( started_at: started_at )
291
- result[ :recovery ] = "git rebase #{config.git_remote}/#{main} && carson deliver" if evaluation[ :cause ] == "freshness"
291
+ result[ :recovery ] = "refresh this branch onto #{config.git_remote}/#{main}, then carson deliver" if evaluation[ :cause ] == "freshness"
292
292
  apply_handoff!(
293
293
  result: result,
294
294
  reason: evaluation.fetch( :reason ),
@@ -742,7 +742,7 @@ module Carson
742
742
  end
743
743
 
744
744
  def deliver_handoff_next_steps
745
- [ "carson status", "carson deliver", "carson govern --loop 300" ]
745
+ [ "carson status", "carson deliver" ]
746
746
  end
747
747
 
748
748
  def deliver_ci_poll_seconds
@@ -809,10 +809,9 @@ module Carson
809
809
 
810
810
  def freshness_recovery( freshness: )
811
811
  remote_ref = freshness.fetch( :remote_ref )
812
- return "git rebase #{remote_ref} && carson deliver" if freshness.fetch( :status ) == :behind
812
+ return "refresh this branch onto #{remote_ref}, then carson deliver" if freshness.fetch( :status ) == :behind
813
813
 
814
- remote, main = remote_ref.split( "/", 2 )
815
- "git fetch #{remote} #{main} && carson deliver"
814
+ "carson deliver (once #{remote_ref} is reachable)"
816
815
  end
817
816
 
818
817
  def deliver_merge_attempt_cap
@@ -898,7 +897,7 @@ module Carson
898
897
  reason: "freshness_behind",
899
898
  cause: "freshness",
900
899
  summary: "branch is behind #{remote_main}",
901
- recovery: "git rebase #{remote_main} && carson deliver"
900
+ recovery: "refresh this branch onto #{remote_main}, then carson deliver"
902
901
  } if merge_state == "BEHIND"
903
902
 
904
903
  return {
@@ -1127,7 +1126,7 @@ module Carson
1127
1126
 
1128
1127
  if lease_stderr.to_s.include?( "stale info" )
1129
1128
  result[ :error ] = "force-with-lease rejected — another push landed on #{branch} since your last fetch"
1130
- result[ :recovery ] = "git fetch #{remote} #{branch} && carson deliver"
1129
+ result[ :recovery ] = "inspect newer commits on #{branch}, reconcile, then carson deliver"
1131
1130
  else
1132
1131
  error_text = lease_stderr.to_s.strip
1133
1132
  error_text = "push failed (force-with-lease)" if error_text.empty?
@@ -1175,7 +1174,7 @@ module Carson
1175
1174
  error_text = stderr.to_s.strip
1176
1175
  error_text = "pr create failed" if error_text.empty?
1177
1176
  result[ :error ] = error_text
1178
- result[ :recovery ] = "gh pr create --title '#{pr_title}' --head #{branch}"
1177
+ result[ :recovery ] = "carson deliver"
1179
1178
  return [ nil, nil ]
1180
1179
  end
1181
1180
 
@@ -1274,7 +1273,7 @@ module Carson
1274
1273
  error_text = stderr.to_s.strip
1275
1274
  error_text = "merge failed" if error_text.empty?
1276
1275
  result[ :error ] = error_text
1277
- result[ :recovery ] = "gh pr merge #{number} --#{method}"
1276
+ result[ :recovery ] = "carson deliver"
1278
1277
  EXIT_ERROR
1279
1278
  end
1280
1279
  end
@@ -23,73 +23,6 @@ module Carson
23
23
  housekeep_one( repo_path: canonical, json_output: json_output )
24
24
  end
25
25
 
26
- # Resolves a target name to a governed repo, then serves it.
27
- def housekeep_target!( target:, json_output: false, dry_run: false )
28
- repo_path = resolve_governed_repo( target: target )
29
- unless repo_path
30
- result = { command: "housekeep", status: "error", error: "Not a governed repository: #{target}", recovery: "Run carson repos to see governed repositories." }
31
- return housekeep_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
32
- end
33
-
34
- if dry_run
35
- scoped = Runtime.new( repo_root: repo_path, tool_root: tool_root, output: output, error: error, verbose: verbose? )
36
- return scoped.housekeep_one_dry_run
37
- end
38
-
39
- housekeep_one( repo_path: repo_path, json_output: json_output )
40
- end
41
-
42
- # Knocks each governed repo's gate in turn.
43
- def housekeep_all!( json_output: false, dry_run: false )
44
- repos = config.govern_repos
45
- if repos.empty?
46
- result = { command: "housekeep", status: "error", error: "No governed repositories configured.", recovery: "Run carson onboard in each repo to register." }
47
- return housekeep_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
48
- end
49
-
50
- if dry_run
51
- repos.each_with_index do |repo_path, idx|
52
- puts_line "" if idx > 0
53
- unless Dir.exist?( repo_path )
54
- puts_line "#{File.basename( repo_path )}: SKIP (path not found)"
55
- next
56
- end
57
- scoped = Runtime.new( repo_root: repo_path, tool_root: tool_root, output: output, error: error, verbose: verbose? )
58
- scoped.housekeep_one_dry_run
59
- end
60
- total = repos.size
61
- puts_line ""
62
- puts_line "#{total} repo#{plural_suffix( count: total )} surveyed. Run without --dry-run to apply."
63
- return EXIT_OK
64
- end
65
-
66
- results = []
67
- repos.each do |repo_path|
68
- entry = housekeep_one_entry( repo_path: repo_path, silent: json_output )
69
- if entry[ :status ] == "ok"
70
- clear_batch_success( command: "housekeep", repo_path: repo_path )
71
- else
72
- record_batch_skip( command: "housekeep", repo_path: repo_path, reason: entry[ :error ] || "housekeep failed" )
73
- end
74
- results << entry
75
- end
76
-
77
- succeeded = results.count { |entry| entry[ :status ] == "ok" }
78
- failed = results.count { |entry| entry[ :status ] != "ok" }
79
- result = { command: "housekeep", status: failed.zero? ? "ok" : "partial", repos: results, succeeded: succeeded, failed: failed }
80
- housekeep_finish( result: result, exit_code: failed.zero? ? EXIT_OK : EXIT_ERROR, json_output: json_output, results: results, succeeded: succeeded, failed: failed )
81
- end
82
-
83
- def housekeep_loop!( json_output:, dry_run:, loop_seconds: )
84
- run_signal_aware_loop!(
85
- loop_name: "housekeep",
86
- loop_seconds: loop_seconds,
87
- cycle_line: ->( cycle_count ) { "housekeep cycle #{cycle_count} at #{Time.now.utc.strftime( '%Y-%m-%d %H:%M:%S UTC' )}" }
88
- ) do
89
- housekeep_all!( json_output: json_output, dry_run: dry_run )
90
- end
91
- end
92
-
93
26
  # Prints a dry-run plan for this repo without making any changes.
94
27
  # Calls reap_dead_worktrees_plan and prune_plan on self (already scoped to the repo).
95
28
  def housekeep_one_dry_run
@@ -185,17 +118,6 @@ module Carson
185
118
  { name: repo_name, path: repo_path, status: "error", error: exception.message }
186
119
  end
187
120
 
188
- # Resolves a user-supplied target to a governed repository path.
189
- # Accepts: exact path, expandable path, or basename match (case-insensitive).
190
- def resolve_governed_repo( target: )
191
- repos = config.govern_repos
192
- expanded = File.expand_path( target )
193
- return expanded if repos.include?( expanded )
194
-
195
- downcased = File.basename( target ).downcase
196
- repos.find { |repo_path| File.basename( repo_path ).downcase == downcased }
197
- end
198
-
199
121
  # Unified output — JSON or human-readable.
200
122
  def housekeep_finish( result:, exit_code:, json_output:, results: nil, succeeded: nil, failed: nil )
201
123
  result[ :exit_code ] = exit_code
@@ -4,12 +4,12 @@ require "json"
4
4
 
5
5
  module Carson
6
6
  class Runtime
7
- module Repos
8
- def repos!( json_output: false )
7
+ module List
8
+ def list!( json_output: false )
9
9
  repos = config.govern_repos
10
10
 
11
11
  if json_output
12
- output.puts JSON.pretty_generate( { command: "repos", repos: repos } )
12
+ output.puts JSON.pretty_generate( { command: "list", repos: repos } )
13
13
  else
14
14
  if repos.empty?
15
15
  puts_line "No governed repositories."
@@ -24,6 +24,6 @@ module Carson
24
24
  end
25
25
  end
26
26
 
27
- include Repos
27
+ include List
28
28
  end
29
29
  end
@@ -57,7 +57,7 @@ module Carson
57
57
 
58
58
  # Canonical hook template location inside Carson repository.
59
59
  def hook_template_path( hook_name: )
60
- File.join( tool_root, "hooks", hook_name )
60
+ File.join( tool_root, "config", ".github", "hooks", hook_name )
61
61
  end
62
62
 
63
63
  # Reports full hook health and can enforce stricter action messaging in `check`.
@@ -144,56 +144,6 @@ module Carson
144
144
  failed.zero? && pending.zero? ? EXIT_OK : EXIT_ERROR
145
145
  end
146
146
 
147
- def prune_all!
148
- repos = config.govern_repos
149
- if repos.empty?
150
- puts_line "No governed repositories configured."
151
- puts_line " Run carson onboard in each repo to register."
152
- return EXIT_ERROR
153
- end
154
-
155
- puts_line ""
156
- puts_line "Prune all (#{repos.length} repo#{plural_suffix( count: repos.length )})"
157
- succeeded = 0
158
- failed = 0
159
-
160
- repos.each do |repo_path|
161
- repo_name = File.basename( repo_path )
162
- unless Dir.exist?( repo_path )
163
- puts_line "#{repo_name}: not found"
164
- record_batch_skip( command: "prune", repo_path: repo_path, reason: "path not found" )
165
- failed += 1
166
- next
167
- end
168
-
169
- begin
170
- buffer = verbose? ? output : StringIO.new
171
- error_buffer = verbose? ? error : StringIO.new
172
- scoped_runtime = Runtime.new( repo_root: repo_path, tool_root: tool_root, output: buffer, error: error_buffer, verbose: verbose? )
173
- status = scoped_runtime.prune!
174
- unless verbose?
175
- summary = buffer.string.lines.last.to_s.strip
176
- puts_line "#{repo_name}: #{summary.empty? ? 'OK' : summary}"
177
- end
178
- if status == EXIT_ERROR
179
- record_batch_skip( command: "prune", repo_path: repo_path, reason: "prune failed" )
180
- failed += 1
181
- else
182
- clear_batch_success( command: "prune", repo_path: repo_path )
183
- succeeded += 1
184
- end
185
- rescue StandardError => exception
186
- puts_line "#{repo_name}: could not complete (#{exception.message})"
187
- record_batch_skip( command: "prune", repo_path: repo_path, reason: exception.message )
188
- failed += 1
189
- end
190
- end
191
-
192
- puts_line ""
193
- puts_line "Prune all complete: #{succeeded} pruned, #{failed} failed."
194
- failed.zero? ? EXIT_OK : EXIT_ERROR
195
- end
196
-
197
147
  # Removes Carson-managed repository integration so a host repository can retire Carson cleanly.
198
148
  def offboard!
199
149
  puts_verbose ""
@@ -60,53 +60,6 @@ module Carson
60
60
  git_system!( "switch", start_branch ) if switched && branch_exists?( branch_name: start_branch )
61
61
  end
62
62
 
63
- # Syncs main branch across all governed repositories.
64
- def sync_all!
65
- repos = config.govern_repos
66
- if repos.empty?
67
- puts_line "No governed repositories configured."
68
- puts_line " Run carson onboard in each repo to register."
69
- return EXIT_ERROR
70
- end
71
-
72
- puts_line ""
73
- puts_line "Sync all (#{repos.length} repo#{plural_suffix( count: repos.length )})"
74
- synced = 0
75
- failed = 0
76
-
77
- repos.each do |repo_path|
78
- repo_name = File.basename( repo_path )
79
- unless Dir.exist?( repo_path )
80
- puts_line "#{repo_name}: not found"
81
- record_batch_skip( command: "sync", repo_path: repo_path, reason: "path not found" )
82
- failed += 1
83
- next
84
- end
85
-
86
- begin
87
- scoped_runtime = build_scoped_runtime( repo_path: repo_path )
88
- status = scoped_runtime.sync!
89
- if status == EXIT_OK
90
- puts_line "#{repo_name}: ok" unless verbose?
91
- clear_batch_success( command: "sync", repo_path: repo_path )
92
- synced += 1
93
- else
94
- puts_line "#{repo_name}: could not sync" unless verbose?
95
- record_batch_skip( command: "sync", repo_path: repo_path, reason: "sync failed" )
96
- failed += 1
97
- end
98
- rescue StandardError => exception
99
- puts_line "#{repo_name}: could not sync (#{exception.message})"
100
- record_batch_skip( command: "sync", repo_path: repo_path, reason: exception.message )
101
- failed += 1
102
- end
103
- end
104
-
105
- puts_line ""
106
- puts_line "Sync all complete: #{synced} synced, #{failed} failed."
107
- failed.zero? ? EXIT_OK : EXIT_ERROR
108
- end
109
-
110
63
  private
111
64
 
112
65
  # Runs a git command, suppressing stdout/stderr in JSON mode to keep output clean.
@@ -181,7 +134,7 @@ module Carson
181
134
  end
182
135
 
183
136
  def main_worktree_context?
184
- realpath_safe( repo_root ) == realpath_safe( main_worktree_root )
137
+ realpath_safe( work_dir ) == realpath_safe( main_worktree_root )
185
138
  end
186
139
 
187
140
  def inside_git_work_tree?
@@ -53,53 +53,6 @@ module Carson
53
53
  ( drift_count + stale_count ).positive? ? EXIT_BLOCK : EXIT_OK
54
54
  end
55
55
 
56
- # Read-only template drift check across all governed repositories.
57
- def template_check_all!
58
- repos = config.govern_repos
59
- if repos.empty?
60
- puts_line "No governed repositories configured."
61
- puts_line " Run carson onboard in each repo to register."
62
- return EXIT_ERROR
63
- end
64
-
65
- puts_line ""
66
- puts_line "Template check all (#{repos.length} repo#{plural_suffix( count: repos.length )})"
67
- in_sync = 0
68
- drifted = 0
69
- failed = 0
70
-
71
- repos.each do |repo_path|
72
- repo_name = File.basename( repo_path )
73
- unless Dir.exist?( repo_path )
74
- puts_line "#{repo_name}: not found"
75
- record_batch_skip( command: "template_check", repo_path: repo_path, reason: "path not found" )
76
- failed += 1
77
- next
78
- end
79
-
80
- begin
81
- scoped_runtime = build_scoped_runtime( repo_path: repo_path )
82
- status = scoped_runtime.template_check!
83
- if status == EXIT_OK
84
- puts_line "#{repo_name}: in sync" unless verbose?
85
- clear_batch_success( command: "template_check", repo_path: repo_path )
86
- in_sync += 1
87
- else
88
- puts_line "#{repo_name}: DRIFT" unless verbose?
89
- drifted += 1
90
- end
91
- rescue StandardError => exception
92
- puts_line "#{repo_name}: could not complete (#{exception.message})"
93
- record_batch_skip( command: "template_check", repo_path: repo_path, reason: exception.message )
94
- failed += 1
95
- end
96
- end
97
-
98
- puts_line ""
99
- puts_line "Template check complete: #{in_sync} in sync, #{drifted} drifted, #{failed} failed."
100
- drifted.zero? && failed.zero? ? EXIT_OK : EXIT_BLOCK
101
- end
102
-
103
56
  # Applies managed template files as full-file writes from Carson sources.
104
57
  # Also removes superseded files that are no longer part of the managed set.
105
58
  def template_apply!( push_prep: false )
@@ -1,46 +1,47 @@
1
- # Carson governportfolio-wide oversight over branch deliveries.
2
- # Govern reassesses queued/gated deliveries, records revision cycles, and integrates one ready delivery at a time.
1
+ # Carson receivesingle-repo delivery triage.
2
+ # Receive reassesses queued/gated deliveries, records revision cycles, and integrates one ready delivery at a time.
3
3
  require "json"
4
4
  require "time"
5
5
 
6
6
  module Carson
7
7
  class Runtime
8
- module Govern
9
- # Portfolio-level entry point. Scans governed repos (or the current repo) and advances deliveries.
10
- def govern!( dry_run: false, json_output: false, loop_seconds: nil )
8
+ module Receive
9
+ # Single-repo entry point. Advances deliveries for the current repository.
10
+ def receive!( dry_run: false, json_output: false, loop_seconds: nil )
11
11
  if loop_seconds
12
- govern_loop!( dry_run: dry_run, json_output: json_output, loop_seconds: loop_seconds )
12
+ receive_loop!( dry_run: dry_run, json_output: json_output, loop_seconds: loop_seconds )
13
13
  else
14
- govern_cycle!( dry_run: dry_run, json_output: json_output )
14
+ receive_cycle!( dry_run: dry_run, json_output: json_output )
15
15
  end
16
16
  end
17
17
 
18
- def govern_cycle!( dry_run:, json_output: )
19
- repositories = governed_repo_paths
20
- repositories = [ repository_record.path ] if repositories.empty?
21
- print_header "Governing #{repositories.length} repo#{plural_suffix( count: repositories.length )}" unless json_output
18
+ def receive_cycle!( dry_run:, json_output: )
19
+ repo_path = repository_record.path
20
+ repo_name = File.basename( repo_path )
21
+ print_header "Receiving #{repo_name}" unless json_output
22
22
 
23
+ repo_report = receive_repo!( repo_path: repo_path, dry_run: dry_run, silent: json_output )
23
24
  report = {
24
25
  cycle_at: Time.now.utc.iso8601,
25
26
  dry_run: dry_run,
26
- repositories: repositories.map { |path| govern_repo!( repo_path: path, dry_run: dry_run, silent: json_output ) }
27
+ repository: repo_report
27
28
  }
28
29
 
29
30
  if json_output
30
31
  output.puts JSON.pretty_generate( report )
31
32
  else
32
- print_govern_summary( report: report )
33
+ print_receive_summary( repo_report: repo_report )
33
34
  end
34
35
 
35
36
  EXIT_OK
36
37
  rescue StandardError => exception
37
- puts_line "Govern did not complete: #{exception.message}"
38
+ puts_line "Receive did not complete: #{exception.message}"
38
39
  EXIT_ERROR
39
40
  end
40
41
 
41
- def govern_loop!( dry_run:, json_output:, loop_seconds: )
42
+ def receive_loop!( dry_run:, json_output:, loop_seconds: )
42
43
  run_signal_aware_loop!(
43
- loop_name: "govern",
44
+ loop_name: "receive",
44
45
  loop_seconds: loop_seconds,
45
46
  cycle_line: ->( cycle_count ) { "cycle #{cycle_count} at #{Time.now.utc.strftime( '%Y-%m-%d %H:%M:%S UTC' )}" },
46
47
  sleep_line: ->( seconds ) do
@@ -48,21 +49,13 @@ module Carson
48
49
  "sleeping #{seconds}s — next cycle at #{next_at.strftime( '%Y-%m-%d %H:%M:%S %z' )}"
49
50
  end
50
51
  ) do
51
- govern_cycle!( dry_run: dry_run, json_output: json_output )
52
+ receive_cycle!( dry_run: dry_run, json_output: json_output )
52
53
  end
53
54
  end
54
55
 
55
56
  private
56
57
 
57
- def governed_repo_paths
58
- config.govern_repos.map do |path|
59
- expanded = File.expand_path( path )
60
- next nil unless Dir.exist?( expanded )
61
- expanded
62
- end.compact
63
- end
64
-
65
- def govern_repo!( repo_path:, dry_run:, silent: false )
58
+ def receive_repo!( repo_path:, dry_run:, silent: false )
66
59
  scoped_runtime = repo_runtime_for( repo_path: repo_path )
67
60
  repository = Repository.new( path: repo_path, runtime: scoped_runtime )
68
61
  deliveries = scoped_runtime.ledger.active_deliveries( repo_path: repo_path )
@@ -365,10 +358,10 @@ module Carson
365
358
  def delivery_action_hint( delivery:, next_to_integrate:, dry_run: )
366
359
  return nil if dry_run
367
360
  return nil if delivery.superseded? || delivery.integrated? || delivery.failed?
368
- return "integrating" if delivery.ready? && delivery.key == next_to_integrate
361
+ return "integrating..." if delivery.ready? && delivery.key == next_to_integrate
369
362
  return nil unless delivery.blocked?
370
363
  return nil if held_delivery?( delivery: delivery )
371
- delivery.revision_count >= 3 ? "escalating" : "revising"
364
+ delivery.revision_count >= 3 ? "escalating..." : "revising..."
372
365
  end
373
366
 
374
367
  def housekeep_repo!( repo_path: )
@@ -514,28 +507,26 @@ module Carson
514
507
  ""
515
508
  end
516
509
 
517
- def print_govern_summary( report: )
518
- Array( report[ :repositories ] ).each do |repo_report|
519
- if repo_report[ :error ]
520
- puts_line "#{repo_report[ :repository ]}: #{repo_report[ :error ]}"
521
- next
522
- end
510
+ def print_receive_summary( repo_report: )
511
+ if repo_report[ :error ]
512
+ puts_line "#{repo_report[ :repository ]}: #{repo_report[ :error ]}"
513
+ return
514
+ end
523
515
 
524
- next if repo_report[ :deliveries ].empty?
516
+ return if repo_report[ :deliveries ].empty?
525
517
 
526
- repo_report[ :deliveries ].each do |delivery|
527
- action_text = format_govern_action( status: delivery[ :status ], action: delivery[ :action ], cause: delivery[ :cause ] )
528
- puts_line "#{repo_report[ :repository ]}/#{delivery[ :branch ]} — #{action_text}"
529
- puts_line " #{delivery[ :summary ]}" unless delivery[ :summary ].to_s.empty?
530
- puts_line " Merge proof: #{delivery.dig( :merge_proof, :summary )}" if delivery[ :merge_proof ]
531
- end
532
- end
518
+ repo_report[ :deliveries ].each do |delivery|
519
+ action_text = format_receive_action( status: delivery[ :status ], action: delivery[ :action ], cause: delivery[ :cause ] )
520
+ puts_line "#{repo_report[ :repository ]}/#{delivery[ :branch ]} — #{action_text}"
521
+ puts_line " #{delivery[ :summary ]}" unless delivery[ :summary ].to_s.empty?
522
+ puts_line " Merge proof: #{delivery.dig( :merge_proof, :summary )}" if delivery[ :merge_proof ]
533
523
  end
524
+ end
534
525
 
535
- def format_govern_action( status:, action:, cause: )
526
+ def format_receive_action( status:, action:, cause: )
536
527
  case action
537
528
  when "integrate"
538
- format_govern_integration_outcome( status: status, cause: cause )
529
+ format_receive_integration_outcome( status: status, cause: cause )
539
530
  when "would_integrate" then "ready to integrate (dry run)"
540
531
  when "hold" then cause == "freshness" ? "refresh required" : "held at gate"
541
532
  when "would_hold" then cause == "freshness" ? "would require refresh (dry run)" : "would hold at gate (dry run)"
@@ -547,7 +538,7 @@ module Carson
547
538
  end
548
539
  end
549
540
 
550
- def format_govern_integration_outcome( status:, cause: )
541
+ def format_receive_integration_outcome( status:, cause: )
551
542
  case status
552
543
  when "integrated" then "integrated"
553
544
  when "gated" then cause == "freshness" ? "refresh required" : "held at gate"
@@ -558,6 +549,6 @@ module Carson
558
549
  end
559
550
  end
560
551
 
561
- include Govern
552
+ include Receive
562
553
  end
563
554
  end
@@ -2,7 +2,7 @@
2
2
  module Carson
3
3
  class Runtime
4
4
  module Recover
5
- GOVERNANCE_SURFACE_PREFIXES = %w[ .github/ hooks/ ].freeze
5
+ GOVERNANCE_SURFACE_PREFIXES = %w[ .github/ config/.github/ ].freeze
6
6
 
7
7
  def recover!( check_name:, json_output: false )
8
8
  result = {
@@ -71,7 +71,7 @@ module Carson
71
71
 
72
72
  unless relation.fetch( :related )
73
73
  result[ :error ] = "branch does not touch the governance surface for #{check_name}"
74
- result[ :recovery ] = "update the branch to repair .github/ or hooks/, then rerun carson recover --check #{check_name.inspect}"
74
+ result[ :recovery ] = "update the branch to repair .github/ or config/.github/, then rerun carson recover --check #{check_name.inspect}"
75
75
  return recover_finish( result: result, exit_code: EXIT_BLOCK, json_output: json_output )
76
76
  end
77
77
 
@@ -15,40 +15,6 @@ module Carson
15
15
  EXIT_OK
16
16
  end
17
17
 
18
- # Portfolio-wide status overview across all governed repositories.
19
- def status_all!( json_output: false )
20
- repositories = config.govern_repos
21
- if repositories.empty?
22
- puts_line "No governed repositories configured."
23
- puts_line " Run carson onboard in each repo to register."
24
- return EXIT_ERROR
25
- end
26
-
27
- results = repositories.map do |repo_path|
28
- repo_name = File.basename( repo_path )
29
- unless Dir.exist?( repo_path )
30
- { name: repo_name, status: "error", error: "not found" }
31
- else
32
- begin
33
- scoped_runtime = build_scoped_runtime( repo_path: repo_path )
34
- { name: repo_name, status: "ok" }.merge( scoped_runtime.send( :gather_status ) )
35
- rescue StandardError => exception
36
- { name: repo_name, status: "error", error: exception.message }
37
- end
38
- end
39
- end
40
-
41
- if json_output
42
- output.puts JSON.pretty_generate( { command: "status", repos: results, repositories: results } )
43
- else
44
- puts_line "Carson #{Carson::VERSION} — Portfolio (#{repositories.length} repo#{plural_suffix( count: repositories.length )})"
45
- puts_line ""
46
- results.each { |result| print_portfolio_status( result: result ) }
47
- end
48
-
49
- EXIT_OK
50
- end
51
-
52
18
  private
53
19
 
54
20
  def gather_status
@@ -108,7 +74,7 @@ module Carson
108
74
  end
109
75
 
110
76
  def main_worktree_context?
111
- realpath_safe( repo_root ) == realpath_safe( main_worktree_root )
77
+ realpath_safe( work_dir ) == realpath_safe( main_worktree_root )
112
78
  end
113
79
 
114
80
  def remote_sync_status( branch: )
@@ -181,18 +147,6 @@ module Carson
181
147
  end
182
148
  end
183
149
 
184
- def print_portfolio_status( result: )
185
- if result.fetch( :status ) == "error"
186
- puts_line "#{result.fetch( :name )}: #{result.fetch( :error )}"
187
- return
188
- end
189
-
190
- deliveries = Array( result.fetch( :branches, [] ) )
191
- counts = deliveries.each_with_object( Hash.new( 0 ) ) { |delivery, memo| memo[ delivery.fetch( :delivery_state ) ] += 1 }
192
- summary = counts.empty? ? "no active deliveries" : counts.map { |state, count| "#{count} #{state}" }.join( ", " )
193
- puts_line "#{result.fetch( :name )} — #{summary}"
194
- end
195
-
196
150
  def format_sync( sync: )
197
151
  case sync
198
152
  when :in_sync then "in sync with remote"