carson 3.22.0 → 3.22.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.
@@ -56,7 +56,7 @@ module Carson
56
56
 
57
57
  EXIT_OK
58
58
  rescue StandardError => exception
59
- puts_line "ERROR: govern failed #{exception.message}"
59
+ puts_line "Govern did not complete: #{exception.message}"
60
60
  EXIT_ERROR
61
61
  end
62
62
 
@@ -70,7 +70,7 @@ module Carson
70
70
  begin
71
71
  govern_cycle!( dry_run: dry_run, json_output: json_output )
72
72
  rescue StandardError => exception
73
- puts_line "ERROR: cycle #{cycle_count} failed #{exception.message}"
73
+ puts_line "Cycle #{cycle_count} did not complete: #{exception.message}"
74
74
  end
75
75
  puts_line "sleeping #{loop_seconds}s until next cycle…"
76
76
  sleep loop_seconds
@@ -88,7 +88,7 @@ module Carson
88
88
  config.govern_repos.map do |path|
89
89
  expanded = File.expand_path( path )
90
90
  unless Dir.exist?( expanded )
91
- puts_line "WARN: governed repo path does not exist: #{expanded}"
91
+ puts_line "Skipping #{expanded} path not found"
92
92
  next nil
93
93
  end
94
94
  expanded
@@ -107,14 +107,14 @@ module Carson
107
107
 
108
108
  unless Dir.exist?( repo_path )
109
109
  repo_report[ :error ] = "path does not exist"
110
- puts_line "ERROR: #{repo_path} does not exist"
110
+ puts_line "#{repo_path}: path not found, skipping"
111
111
  return repo_report
112
112
  end
113
113
 
114
114
  prs = list_open_prs( repo_path: repo_path )
115
115
  if prs.nil?
116
116
  repo_report[ :error ] = "failed to list open PRs"
117
- puts_line "ERROR: failed to list open PRs for #{repo_path}"
117
+ puts_line "#{File.basename(repo_path)}: unable to list open PRs"
118
118
  return repo_report
119
119
  end
120
120
 
@@ -196,8 +196,12 @@ module Carson
196
196
  if review_decision == "CHANGES_REQUESTED"
197
197
  return [ TRIAGE_REVIEW_BLOCKED, "changes requested by reviewer" ]
198
198
  end
199
+ if review_decision == "REVIEW_REQUIRED"
200
+ return [ TRIAGE_REVIEW_BLOCKED, "review required" ]
201
+ end
199
202
 
200
203
  review_status, review_detail = check_review_gate_status( pr: pr, repo_path: repo_path )
204
+ return [ TRIAGE_NEEDS_ATTENTION, review_detail ] if review_status == :error
201
205
  return [ TRIAGE_REVIEW_BLOCKED, review_detail ] unless review_status == :pass
202
206
 
203
207
  [ TRIAGE_READY, "all gates pass" ]
@@ -231,17 +235,25 @@ module Carson
231
235
 
232
236
  # Checks review gate status. Returns [:pass/:fail, detail].
233
237
  def check_review_gate_status( pr:, repo_path: )
234
- review_decision = pr[ "reviewDecision" ].to_s.upcase
235
- case review_decision
236
- when "APPROVED"
237
- [ :pass, "approved" ]
238
- when "CHANGES_REQUESTED"
239
- [ :fail, "changes requested" ]
240
- when "REVIEW_REQUIRED"
241
- [ :fail, "review required" ]
242
- else
243
- [ :pass, "no review policy or approved" ]
244
- end
238
+ repo_runtime = scoped_runtime( repo_path: repo_path )
239
+ owner, repo = repo_runtime.send( :repository_coordinates )
240
+ report = repo_runtime.send(
241
+ :review_gate_report_for_pr,
242
+ owner: owner,
243
+ repo: repo,
244
+ pr_number: pr.fetch( "number" ),
245
+ branch_name: pr.fetch( "headRefName" ).to_s,
246
+ pr_summary: {
247
+ number: pr.fetch( "number" ),
248
+ title: pr.fetch( "title" ).to_s,
249
+ url: pr.fetch( "url" ).to_s,
250
+ state: "OPEN"
251
+ }
252
+ )
253
+ result = repo_runtime.send( :review_gate_result, report: report )
254
+ [ result.fetch( :status ), result.fetch( :detail ) ]
255
+ rescue StandardError => exception
256
+ [ :error, "review gate check failed: #{exception.message}" ]
245
257
  end
246
258
 
247
259
  # Maps classification to action.
@@ -296,7 +308,7 @@ module Carson
296
308
  housekeep_repo!( repo_path: repo_path )
297
309
  else
298
310
  error_text = stderr_text.to_s.strip
299
- puts_line " merge failed: #{error_text}"
311
+ puts_line " merge did not succeed: #{error_text}"
300
312
  end
301
313
  end
302
314
 
@@ -10,29 +10,52 @@ module Carson
10
10
  class Runtime
11
11
  module Housekeep
12
12
  # Serves the current repo: sync + prune.
13
- def housekeep!( json_output: false )
13
+ def housekeep!( json_output: false, dry_run: false )
14
+ return housekeep_one_dry_run if dry_run
15
+
14
16
  housekeep_one( repo_path: repo_root, json_output: json_output )
15
17
  end
16
18
 
17
19
  # Resolves a target name to a governed repo, then serves it.
18
- def housekeep_target!( target:, json_output: false )
20
+ def housekeep_target!( target:, json_output: false, dry_run: false )
19
21
  repo_path = resolve_governed_repo( target: target )
20
22
  unless repo_path
21
23
  result = { command: "housekeep", status: "error", error: "Not a governed repository: #{target}", recovery: "Run carson repos to see governed repositories." }
22
24
  return housekeep_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
23
25
  end
24
26
 
27
+ if dry_run
28
+ scoped = Runtime.new( repo_root: repo_path, tool_root: tool_root, output: output, error: error, verbose: verbose? )
29
+ return scoped.housekeep_one_dry_run
30
+ end
31
+
25
32
  housekeep_one( repo_path: repo_path, json_output: json_output )
26
33
  end
27
34
 
28
35
  # Knocks each governed repo's gate in turn.
29
- def housekeep_all!( json_output: false )
36
+ def housekeep_all!( json_output: false, dry_run: false )
30
37
  repos = config.govern_repos
31
38
  if repos.empty?
32
39
  result = { command: "housekeep", status: "error", error: "No governed repositories configured.", recovery: "Run carson onboard in each repo to register." }
33
40
  return housekeep_finish( result: result, exit_code: EXIT_ERROR, json_output: json_output )
34
41
  end
35
42
 
43
+ if dry_run
44
+ repos.each_with_index do |repo_path, idx|
45
+ puts_line "" if idx > 0
46
+ unless Dir.exist?( repo_path )
47
+ puts_line "#{File.basename( repo_path )}: SKIP (path not found)"
48
+ next
49
+ end
50
+ scoped = Runtime.new( repo_root: repo_path, tool_root: tool_root, output: output, error: error, verbose: verbose? )
51
+ scoped.housekeep_one_dry_run
52
+ end
53
+ total = repos.size
54
+ puts_line ""
55
+ puts_line "#{total} repo#{plural_suffix( count: total )} surveyed. Run without --dry-run to apply."
56
+ return EXIT_OK
57
+ end
58
+
36
59
  results = []
37
60
  repos.each do |repo_path|
38
61
  entry = housekeep_one_entry( repo_path: repo_path, silent: json_output )
@@ -50,55 +73,166 @@ module Carson
50
73
  housekeep_finish( result: result, exit_code: failed.zero? ? EXIT_OK : EXIT_ERROR, json_output: json_output, results: results, succeeded: succeeded, failed: failed )
51
74
  end
52
75
 
53
- # Removes dead worktrees those whose content is on main or with merged PR evidence.
76
+ # Prints a dry-run plan for this repo without making any changes.
77
+ # Calls reap_dead_worktrees_plan and prune_plan on self (already scoped to the repo).
78
+ def housekeep_one_dry_run
79
+ repo_name = File.basename( repo_root )
80
+ worktree_plan = reap_dead_worktrees_plan
81
+ branch_plan = prune_plan( dry_run: true )
82
+ print_housekeep_dry_run( repo_name: repo_name, worktree_plan: worktree_plan, branch_plan: branch_plan )
83
+ EXIT_OK
84
+ end
85
+
86
+ # Returns a plan array describing what reap_dead_worktrees! would do for each
87
+ # non-main worktree, without executing any mutations.
88
+ # Each item: { name:, branch:, action: :reap|:skip, reason: }
89
+ def reap_dead_worktrees_plan
90
+ main_root = main_worktree_root
91
+ items = []
92
+
93
+ agent_prefixes = Worktree::AGENT_DIRS.filter_map do |dir|
94
+ full = File.join( main_root, dir, "worktrees" )
95
+ File.join( realpath_safe( full ), "" ) if Dir.exist?( full )
96
+ end
97
+
98
+ worktree_list.each do |worktree|
99
+ next if worktree.path == main_root
100
+ next unless worktree.branch
101
+
102
+ item = { name: File.basename( worktree.path ), branch: worktree.branch }
103
+
104
+ if worktree.holds_cwd?
105
+ items << item.merge( action: :skip, reason: "held by current shell" )
106
+ next
107
+ end
108
+
109
+ if worktree.held_by_other_process?
110
+ items << item.merge( action: :skip, reason: "held by another process" )
111
+ next
112
+ end
113
+
114
+ # Missing directory — would be reaped by worktree prune + branch delete.
115
+ unless Dir.exist?( worktree.path )
116
+ items << item.merge( action: :reap, reason: "directory missing (destroyed externally)" )
117
+ next
118
+ end
119
+
120
+ # Layer 1: agent-owned + content absorbed into main (no gh needed).
121
+ if agent_prefixes.any? { |prefix| worktree.path.start_with?( prefix ) } &&
122
+ branch_absorbed_into_main?( branch: worktree.branch )
123
+ items << item.merge( action: :reap, reason: "content absorbed into main" )
124
+ next
125
+ end
126
+
127
+ # Layers 2 + 3: PR evidence — requires gh CLI.
128
+ unless gh_available?
129
+ items << item.merge( action: :skip, reason: "gh CLI not available for PR check" )
130
+ next
131
+ end
132
+
133
+ tip_sha = begin
134
+ git_capture!( "rev-parse", "--verify", worktree.branch ).strip
135
+ rescue StandardError
136
+ nil
137
+ end
138
+
139
+ unless tip_sha
140
+ items << item.merge( action: :skip, reason: "cannot read branch tip SHA" )
141
+ next
142
+ end
143
+
144
+ merged_pr, = merged_pr_for_branch( branch: worktree.branch, branch_tip_sha: tip_sha )
145
+ if merged_pr
146
+ items << item.merge( action: :reap, reason: "merged #{pr_short_ref( merged_pr[ :url ] )}" )
147
+ next
148
+ end
149
+
150
+ if branch_has_open_pr?( branch: worktree.branch )
151
+ items << item.merge( action: :skip, reason: "open PR exists" )
152
+ next
153
+ end
154
+
155
+ abandoned_pr, = abandoned_pr_for_branch( branch: worktree.branch, branch_tip_sha: tip_sha )
156
+ if abandoned_pr
157
+ items << item.merge( action: :reap, reason: "closed abandoned #{pr_short_ref( abandoned_pr[ :url ] )}" )
158
+ next
159
+ end
160
+
161
+ items << item.merge( action: :skip, reason: "no evidence to reap" )
162
+ end
163
+
164
+ items
165
+ end
166
+
167
+ # Removes dead worktrees — those whose content is on main, with merged PR evidence,
168
+ # or with closed-unmerged PR evidence and no open PR.
54
169
  # Unblocks prune for the branches they hold.
55
- # Two-layer dead check:
170
+ # Three-layer dead check:
56
171
  # 1. Content-absorbed: delegates to sweep_stale_worktrees! (shared, no gh needed).
57
172
  # 2. Merged PR evidence: covers rebase/squash where main has since evolved
58
173
  # the same files (requires gh).
174
+ # 3. Abandoned PR evidence: closed-but-unmerged PR on the exact branch tip,
175
+ # but only when no open PR still exists for the branch.
59
176
  def reap_dead_worktrees!
60
177
  # Layer 1: sweep agent-owned worktrees whose content is on main.
61
178
  sweep_stale_worktrees!
62
179
 
63
- # Layer 2: merged PR evidence for remaining worktrees.
180
+ # Layers 2 and 3: PR evidence for remaining worktrees.
64
181
  return unless gh_available?
65
182
 
66
183
  main_root = main_worktree_root
67
184
  worktree_list.each do |worktree|
68
- path = worktree.fetch( :path )
69
- branch = worktree.fetch( :branch, nil )
70
- next if path == main_root
71
- next unless branch
72
- next if cwd_inside_worktree?( worktree_path: path )
185
+ next if worktree.path == main_root
186
+ next unless worktree.branch
187
+ next if worktree.holds_cwd?
188
+ next if worktree.held_by_other_process?
73
189
 
74
190
  # Missing directory: worktree was destroyed externally.
75
191
  # Prune the stale entry and delete the branch immediately.
76
- unless Dir.exist?( path )
192
+ unless Dir.exist?( worktree.path )
77
193
  git_run( "worktree", "prune" )
78
- puts_verbose "reaped stale worktree entry: #{File.basename( path )} (branch: #{branch})"
79
- if !config.protected_branches.include?( branch )
80
- git_run( "branch", "-D", branch )
81
- puts_verbose "deleted branch: #{branch}"
194
+ puts_verbose "reaped stale worktree entry: #{File.basename( worktree.path )} (branch: #{worktree.branch})"
195
+ if !config.protected_branches.include?( worktree.branch )
196
+ git_run( "branch", "-D", worktree.branch )
197
+ puts_verbose "deleted branch: #{worktree.branch}"
82
198
  end
83
199
  next
84
200
  end
85
201
 
86
- tip_sha = git_capture!( "rev-parse", "--verify", branch ).strip rescue nil
202
+ tip_sha = git_capture!( "rev-parse", "--verify", worktree.branch ).strip rescue nil
87
203
  next unless tip_sha
88
204
 
89
- merged_pr, = merged_pr_for_branch( branch: branch, branch_tip_sha: tip_sha )
90
- next if merged_pr.nil?
205
+ merged_pr, = merged_pr_for_branch( branch: worktree.branch, branch_tip_sha: tip_sha )
206
+ if !merged_pr.nil?
207
+ # Remove the worktree (no --force: refuses if dirty working tree).
208
+ _, _, rm_success, = git_run( "worktree", "remove", worktree.path )
209
+ next unless rm_success
210
+
211
+ puts_verbose "reaped dead worktree: #{File.basename( worktree.path )} (branch: #{worktree.branch})"
212
+
213
+ # Delete the local branch now that no worktree holds it.
214
+ if !config.protected_branches.include?( worktree.branch )
215
+ git_run( "branch", "-D", worktree.branch )
216
+ puts_verbose "deleted branch: #{worktree.branch}"
217
+ end
218
+ next
219
+ end
220
+
221
+ next if branch_has_open_pr?( branch: worktree.branch )
222
+
223
+ abandoned_pr, = abandoned_pr_for_branch( branch: worktree.branch, branch_tip_sha: tip_sha )
224
+ next if abandoned_pr.nil?
91
225
 
92
226
  # Remove the worktree (no --force: refuses if dirty working tree).
93
- _, _, rm_success, = git_run( "worktree", "remove", path )
227
+ _, _, rm_success, = git_run( "worktree", "remove", worktree.path )
94
228
  next unless rm_success
95
229
 
96
- puts_verbose "reaped dead worktree: #{File.basename( path )} (branch: #{branch})"
230
+ puts_verbose "reaped abandoned worktree: #{File.basename( worktree.path )} (branch: #{worktree.branch}, closed PR: #{abandoned_pr.fetch( :url )})"
97
231
 
98
232
  # Delete the local branch now that no worktree holds it.
99
- if !config.protected_branches.include?( branch )
100
- git_run( "branch", "-D", branch )
101
- puts_verbose "deleted branch: #{branch}"
233
+ if !config.protected_branches.include?( worktree.branch )
234
+ git_run( "branch", "-D", worktree.branch )
235
+ puts_verbose "deleted branch: #{worktree.branch}"
102
236
  end
103
237
  end
104
238
  end
@@ -139,7 +273,7 @@ module Carson
139
273
 
140
274
  { name: repo_name, path: repo_path, status: ok ? "ok" : "error" }
141
275
  rescue StandardError => exception
142
- puts_line "#{repo_name}: FAIL (#{exception.message})" unless silent
276
+ puts_line "#{repo_name}: did not complete (#{exception.message})" unless silent
143
277
  { name: repo_name, path: repo_path, status: "error", error: exception.message }
144
278
  end
145
279
 
@@ -178,6 +312,78 @@ module Carson
178
312
 
179
313
  exit_code
180
314
  end
315
+
316
+ # Formats and prints the dry-run plan for one repo.
317
+ def print_housekeep_dry_run( repo_name:, worktree_plan:, branch_plan: )
318
+ stale = branch_plan.fetch( :stale, [] )
319
+ orphan = branch_plan.fetch( :orphan, [] )
320
+ absorbed = branch_plan.fetch( :absorbed, [] )
321
+
322
+ all_items = worktree_plan + stale + orphan + absorbed
323
+ would_apply = all_items.count { |i| i[ :action ] == :reap || i[ :action ] == :delete }
324
+ would_skip = all_items.count { |i| i[ :action ] == :skip }
325
+
326
+ # Column widths for aligned output.
327
+ name_width = [ ( worktree_plan + stale + orphan + absorbed ).map { |i| i[ :name ].to_s.length + i[ :branch ].to_s.length }.max || 0, 28 ].min + 2
328
+ reason_width = 34
329
+
330
+ puts_line "Dry run — #{repo_name}"
331
+ note = gh_available? ? nil : " (gh CLI not available — PR evidence skipped)"
332
+ puts_line " Note: branch staleness reflects last sync#{note}."
333
+ puts_line ""
334
+
335
+ puts_line " Worktrees:"
336
+ if worktree_plan.empty?
337
+ puts_line " none"
338
+ else
339
+ worktree_plan.each do |item|
340
+ label = "#{item[ :name ]} (#{item[ :branch ]})"
341
+ action_str = item[ :action ] == :reap ? "→ would reap" : "→ skip"
342
+ puts_line " #{label.ljust( name_width )} #{item[ :reason ].ljust( reason_width )} #{action_str}"
343
+ end
344
+ end
345
+
346
+ puts_line ""
347
+ puts_line " Stale branches (upstream gone):"
348
+ if stale.empty?
349
+ puts_line " none"
350
+ else
351
+ stale.each { |item| print_branch_plan_item( item: item, name_width: name_width, reason_width: reason_width ) }
352
+ end
353
+
354
+ puts_line ""
355
+ puts_line " Orphan branches (no upstream tracking):"
356
+ if orphan.empty?
357
+ puts_line " none"
358
+ else
359
+ orphan.each { |item| print_branch_plan_item( item: item, name_width: name_width, reason_width: reason_width ) }
360
+ end
361
+
362
+ puts_line ""
363
+ puts_line " Absorbed branches (content already on main):"
364
+ if absorbed.empty?
365
+ puts_line " none"
366
+ else
367
+ absorbed.each { |item| print_branch_plan_item( item: item, name_width: name_width, reason_width: reason_width ) }
368
+ end
369
+
370
+ puts_line ""
371
+ puts_line " #{would_apply} would be applied, #{would_skip} skipped."
372
+ puts_line " Run without --dry-run to apply." if would_apply > 0
373
+ end
374
+
375
+ # Prints one branch plan item with aligned columns.
376
+ def print_branch_plan_item( item:, name_width:, reason_width: )
377
+ action_str = item[ :action ] == :delete ? "→ would delete" : "→ skip"
378
+ puts_line " #{item[ :branch ].ljust( name_width )} #{item[ :reason ].ljust( reason_width )} #{action_str}"
379
+ end
380
+
381
+ # Extracts a short PR reference (e.g. "PR #123") from a GitHub URL.
382
+ def pr_short_ref( url )
383
+ return "PR" if url.nil? || url.empty?
384
+ m = url.match( /\/pull\/(\d+)$/ )
385
+ m ? "PR ##{m[1]}" : "PR"
386
+ end
181
387
  end
182
388
 
183
389
  include Housekeep
@@ -12,7 +12,7 @@ module Carson
12
12
  return fingerprint_status unless fingerprint_status.nil?
13
13
 
14
14
  unless inside_git_work_tree?
15
- puts_line "ERROR: #{repo_root} is not a git repository."
15
+ puts_line "#{repo_root} is not a git repository."
16
16
  return EXIT_ERROR
17
17
  end
18
18
 
@@ -38,7 +38,7 @@ module Carson
38
38
  return fingerprint_status unless fingerprint_status.nil?
39
39
 
40
40
  unless inside_git_work_tree?
41
- puts_line "ERROR: #{repo_root} is not a git repository."
41
+ puts_line "#{repo_root} is not a git repository."
42
42
  return EXIT_ERROR
43
43
  end
44
44
 
@@ -49,16 +49,17 @@ module Carson
49
49
  return hook_status unless hook_status == EXIT_OK
50
50
 
51
51
  drift_count = template_results.count { it.fetch( :status ) != "ok" }
52
+ stale_count = template_superseded_present.count
52
53
  template_status = template_apply!
53
54
  return template_status unless template_status == EXIT_OK
54
55
 
55
- @template_sync_result = template_propagate!( drift_count: drift_count )
56
+ @template_sync_result = template_propagate!( drift_count: drift_count + stale_count )
56
57
 
57
58
  audit_status = audit!
58
59
  if audit_status == EXIT_OK
59
60
  puts_line "OK: Carson refresh completed for #{repo_root}."
60
61
  elsif audit_status == EXIT_BLOCK
61
- puts_line "BLOCK: Carson refresh completed with policy blocks; resolve and rerun carson audit."
62
+ puts_line "Refresh complete some checks need attention. Run carson audit for details."
62
63
  end
63
64
  return audit_status
64
65
  end
@@ -69,15 +70,17 @@ module Carson
69
70
  puts_line "Hooks installed (#{config.managed_hooks.count} hooks)."
70
71
 
71
72
  template_drift_count = template_results.count { it.fetch( :status ) != "ok" }
73
+ stale_count = template_superseded_present.count
72
74
  template_status = with_captured_output { template_apply! }
73
75
  return template_status unless template_status == EXIT_OK
74
- if template_drift_count.positive?
75
- puts_line "Templates applied (#{template_drift_count} updated)."
76
+ total_drift = template_drift_count + stale_count
77
+ if total_drift.positive?
78
+ puts_line "Templates applied (#{template_drift_count} updated, #{stale_count} removed)."
76
79
  else
77
80
  puts_line "Templates in sync."
78
81
  end
79
82
 
80
- @template_sync_result = template_propagate!( drift_count: template_drift_count )
83
+ @template_sync_result = template_propagate!( drift_count: total_drift )
81
84
 
82
85
  audit_status = audit!
83
86
  puts_line "Refresh complete."
@@ -109,7 +112,7 @@ module Carson
109
112
  repos.each do |repo_path|
110
113
  repo_name = File.basename( repo_path )
111
114
  unless Dir.exist?( repo_path )
112
- puts_line "#{repo_name}: FAIL (path not found)"
115
+ puts_line "#{repo_name}: not found"
113
116
  record_batch_skip( command: "refresh", repo_path: repo_path, reason: "path not found" )
114
117
  failed += 1
115
118
  next
@@ -157,7 +160,7 @@ module Carson
157
160
  repos.each do |repo_path|
158
161
  repo_name = File.basename( repo_path )
159
162
  unless Dir.exist?( repo_path )
160
- puts_line "#{repo_name}: FAIL (path not found)"
163
+ puts_line "#{repo_name}: not found"
161
164
  record_batch_skip( command: "prune", repo_path: repo_path, reason: "path not found" )
162
165
  failed += 1
163
166
  next
@@ -180,7 +183,7 @@ module Carson
180
183
  succeeded += 1
181
184
  end
182
185
  rescue StandardError => exception
183
- puts_line "#{repo_name}: FAIL (#{exception.message})"
186
+ puts_line "#{repo_name}: could not complete (#{exception.message})"
184
187
  record_batch_skip( command: "prune", repo_path: repo_path, reason: exception.message )
185
188
  failed += 1
186
189
  end
@@ -196,7 +199,7 @@ module Carson
196
199
  puts_verbose ""
197
200
  puts_verbose "[Offboard]"
198
201
  unless inside_git_work_tree?
199
- puts_line "ERROR: #{repo_root} is not a git repository."
202
+ puts_line "#{repo_root} is not a git repository."
200
203
  return EXIT_ERROR
201
204
  end
202
205
  if self.in.respond_to?( :tty? ) && self.in.tty?
@@ -266,15 +269,12 @@ module Carson
266
269
  auto_register_govern!
267
270
 
268
271
  puts_line ""
269
- puts_line "Your repository is set up. Carson has placed files in your"
270
- puts_line "project's .github/ directory pull request templates,"
271
- puts_line "guidelines for AI coding assistants, and any canonical"
272
- puts_line "rules you've configured. Once pushed to GitHub, they'll"
273
- puts_line "ensure every pull request follows a consistent standard"
274
- puts_line "and all checks run automatically."
275
- puts_line ""
276
- puts_line "Before your first push, have a look through .github/ to"
277
- puts_line "make sure everything is to your liking."
272
+ puts_line "Your repository is set up. If you have configured"
273
+ puts_line "lint.canonical, Carson has placed your canonical"
274
+ puts_line "policy files in the project's .github/ directory."
275
+ puts_line "Once pushed to GitHub, they'll ensure every pull"
276
+ puts_line "request follows a consistent standard and all"
277
+ puts_line "checks run automatically."
278
278
  puts_line ""
279
279
  puts_line "To adjust any setting: carson setup"
280
280
 
@@ -322,7 +322,7 @@ module Carson
322
322
  if git_remote_exists?( remote_name: config.git_remote )
323
323
  puts_verbose "remote_ok: #{config.git_remote}"
324
324
  else
325
- puts_line "WARN: remote '#{config.git_remote}' not found; run carson setup to configure."
325
+ puts_line "Remote '#{config.git_remote}' not found run carson setup to configure."
326
326
  end
327
327
  end
328
328
 
@@ -349,7 +349,7 @@ module Carson
349
349
  puts_line "#{repo_name}: #{label}#{sync_suffix}"
350
350
  status
351
351
  rescue StandardError => exception
352
- puts_line "#{repo_name}: FAIL (#{exception.message})"
352
+ puts_line "#{repo_name}: could not complete (#{exception.message})"
353
353
  EXIT_ERROR
354
354
  end
355
355
 
@@ -357,7 +357,7 @@ module Carson
357
357
  case status
358
358
  when EXIT_OK then "OK"
359
359
  when EXIT_BLOCK then "BLOCK"
360
- else "FAIL"
360
+ else "incomplete"
361
361
  end
362
362
  end
363
363
 
@@ -377,7 +377,7 @@ module Carson
377
377
  puts_verbose "hooks_path_unset: core.hooksPath"
378
378
  EXIT_OK
379
379
  rescue StandardError => exception
380
- puts_line "ERROR: unable to update core.hooksPath (#{exception.message})"
380
+ puts_line "Could not update core.hooksPath: #{exception.message}"
381
381
  EXIT_ERROR
382
382
  end
383
383