carson 3.22.0 → 3.23.0

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/API.md +19 -20
  3. data/MANUAL.md +76 -65
  4. data/README.md +42 -50
  5. data/RELEASE.md +24 -1
  6. data/SKILL.md +1 -1
  7. data/VERSION +1 -1
  8. data/carson.gemspec +3 -4
  9. data/hooks/command-guard +1 -1
  10. data/hooks/pre-push +17 -20
  11. data/lib/carson/adapters/agent.rb +2 -2
  12. data/lib/carson/branch.rb +38 -0
  13. data/lib/carson/cli.rb +45 -30
  14. data/lib/carson/config.rb +80 -29
  15. data/lib/carson/delivery.rb +64 -0
  16. data/lib/carson/ledger.rb +305 -0
  17. data/lib/carson/repository.rb +47 -0
  18. data/lib/carson/revision.rb +30 -0
  19. data/lib/carson/runtime/audit.rb +43 -17
  20. data/lib/carson/runtime/deliver.rb +163 -149
  21. data/lib/carson/runtime/govern.rb +233 -357
  22. data/lib/carson/runtime/housekeep.rb +233 -27
  23. data/lib/carson/runtime/local/onboard.rb +29 -29
  24. data/lib/carson/runtime/local/prune.rb +120 -35
  25. data/lib/carson/runtime/local/sync.rb +29 -7
  26. data/lib/carson/runtime/local/template.rb +30 -12
  27. data/lib/carson/runtime/local/worktree.rb +37 -442
  28. data/lib/carson/runtime/review/gate_support.rb +144 -12
  29. data/lib/carson/runtime/review/sweep_support.rb +2 -2
  30. data/lib/carson/runtime/review/utility.rb +1 -1
  31. data/lib/carson/runtime/review.rb +21 -77
  32. data/lib/carson/runtime/setup.rb +25 -33
  33. data/lib/carson/runtime/status.rb +96 -212
  34. data/lib/carson/runtime.rb +39 -4
  35. data/lib/carson/worktree.rb +497 -0
  36. data/lib/carson.rb +6 -0
  37. metadata +37 -17
  38. data/.github/copilot-instructions.md +0 -1
  39. data/.github/pull_request_template.md +0 -12
  40. data/templates/.github/AGENTS.md +0 -1
  41. data/templates/.github/CLAUDE.md +0 -1
  42. data/templates/.github/carson.md +0 -47
  43. data/templates/.github/copilot-instructions.md +0 -1
  44. data/templates/.github/pull_request_template.md +0 -12
@@ -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 )
@@ -44,61 +67,172 @@ module Carson
44
67
  results << entry
45
68
  end
46
69
 
47
- succeeded = results.count { it[ :status ] == "ok" }
48
- failed = results.count { it[ :status ] != "ok" }
70
+ succeeded = results.count { |entry| entry[ :status ] == "ok" }
71
+ failed = results.count { |entry| entry[ :status ] != "ok" }
49
72
  result = { command: "housekeep", status: failed.zero? ? "ok" : "partial", repos: results, succeeded: succeeded, failed: failed }
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.map do |dir|
94
+ full = File.join( main_root, dir, "worktrees" )
95
+ File.join( realpath_safe( full ), "" ) if Dir.exist?( full )
96
+ end.compact
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
 
@@ -21,7 +21,7 @@ module Carson
21
21
  puts_line "Onboarding #{repo_name}..."
22
22
 
23
23
  if !global_config_exists? || !git_remote_exists?( remote_name: config.git_remote )
24
- if self.in.respond_to?( :tty? ) && self.in.tty?
24
+ if input_stream.respond_to?( :tty? ) && input_stream.tty?
25
25
  setup_status = setup!
26
26
  return setup_status unless setup_status == EXIT_OK
27
27
  else
@@ -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
 
@@ -48,17 +48,18 @@ module Carson
48
48
  hook_status = prepare!
49
49
  return hook_status unless hook_status == EXIT_OK
50
50
 
51
- drift_count = template_results.count { it.fetch( :status ) != "ok" }
51
+ drift_count = template_results.count { |entry| entry.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
@@ -68,16 +69,18 @@ module Carson
68
69
  return hook_status unless hook_status == EXIT_OK
69
70
  puts_line "Hooks installed (#{config.managed_hooks.count} hooks)."
70
71
 
71
- template_drift_count = template_results.count { it.fetch( :status ) != "ok" }
72
+ template_drift_count = template_results.count { |entry| entry.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,10 +199,10 @@ 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
- if self.in.respond_to?( :tty? ) && self.in.tty?
205
+ if input_stream.respond_to?( :tty? ) && input_stream.tty?
203
206
  puts_line ""
204
207
  puts_line "This will remove Carson hooks, managed .github/ files,"
205
208
  puts_line "and deregister this repository from portfolio governance."
@@ -248,7 +251,7 @@ module Carson
248
251
  return hook_status unless hook_status == EXIT_OK
249
252
  puts_line "Hooks installed (#{config.managed_hooks.count} hooks)."
250
253
 
251
- template_drift_count = template_results.count { it.fetch( :status ) != "ok" }
254
+ template_drift_count = template_results.count { |entry| entry.fetch( :status ) != "ok" }
252
255
  template_status = with_captured_output { template_apply! }
253
256
  return template_status unless template_status == EXIT_OK
254
257
  if template_drift_count.positive?
@@ -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