carson 2.24.0 → 2.25.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.
@@ -167,7 +167,7 @@ module Carson
167
167
 
168
168
  # Writes review gate artefacts using fixed report names in global report output.
169
169
  def write_review_gate_report( report: )
170
- markdown_path, json_path = write_report(
170
+ markdown_path, json_path = report(
171
171
  report: report,
172
172
  markdown_name: REVIEW_GATE_REPORT_MD,
173
173
  json_name: REVIEW_GATE_REPORT_JSON,
@@ -199,7 +199,7 @@ module Carson
199
199
 
200
200
  # Writes sweep artefacts for CI logs and local troubleshooting.
201
201
  def write_review_sweep_report( report: )
202
- markdown_path, json_path = write_report(
202
+ markdown_path, json_path = report(
203
203
  report: report,
204
204
  markdown_name: REVIEW_SWEEP_REPORT_MD,
205
205
  json_name: REVIEW_SWEEP_REPORT_JSON,
@@ -14,7 +14,7 @@ module Carson
14
14
 
15
15
  # Disposition records always start with configured prefix.
16
16
  def disposition_prefixed?( text: )
17
- text.to_s.lstrip.start_with?( config.review_disposition_prefix )
17
+ text.to_s.lstrip.start_with?( config.review_disposition )
18
18
  end
19
19
 
20
20
  # Extracts first matching disposition token from configured acknowledgement body.
@@ -48,7 +48,7 @@ module Carson
48
48
  end
49
49
 
50
50
  # Shared report writer for JSON plus Markdown pairs in global report output.
51
- def write_report( report:, markdown_name:, json_name:, renderer: )
51
+ def report( report:, markdown_name:, json_name:, renderer: )
52
52
  report_dir = report_dir_path
53
53
  FileUtils.mkdir_p( report_dir )
54
54
  markdown_path = File.join( report_dir, markdown_name )
@@ -127,22 +127,28 @@ module Carson
127
127
  def prompt_workflow_style
128
128
  puts_line ""
129
129
  puts_line "Workflow style"
130
+ current = config.workflow_style
131
+ puts_line " Currently: #{current}" unless current.nil? || current.empty?
130
132
  options = [
131
133
  { label: "branch — enforce PR-only merges (default)", value: "branch" },
132
134
  { label: "trunk — commit directly to main", value: "trunk" }
133
135
  ]
134
- prompt_choice( options: options, default: 0 )
136
+ default_index = options.index { |o| o.fetch( :value ) == current } || 0
137
+ prompt_choice( options: options, default: default_index )
135
138
  end
136
139
 
137
140
  def prompt_merge_method
138
141
  puts_line ""
139
142
  puts_line "Merge method"
143
+ current = config.govern_merge_method
144
+ puts_line " Currently: #{current}" unless current.nil? || current.empty?
140
145
  options = [
141
146
  { label: "squash — one commit per PR (recommended)", value: "squash" },
142
147
  { label: "rebase — linear history, individual commits", value: "rebase" },
143
148
  { label: "merge — merge commits", value: "merge" }
144
149
  ]
145
- prompt_choice( options: options, default: 0 )
150
+ default_index = options.index { |o| o.fetch( :value ) == current } || 0
151
+ prompt_choice( options: options, default: default_index )
146
152
  end
147
153
 
148
154
  def prompt_canonical_template
@@ -216,7 +216,6 @@ module Carson
216
216
  end
217
217
 
218
218
  require_relative "runtime/local"
219
- require_relative "runtime/lint"
220
219
  require_relative "runtime/audit"
221
220
  require_relative "runtime/review"
222
221
  require_relative "runtime/govern"
@@ -24,7 +24,6 @@ carson review gate # block until actionable review findings are resolved
24
24
  ```bash
25
25
  carson sync # fast-forward local main from remote
26
26
  carson prune # remove stale branches (safer than git branch -d on squash repos)
27
- carson housekeep # sync + prune together
28
27
  ```
29
28
 
30
29
  ## Exit Codes
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: 2.24.0
4
+ version: 2.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang
@@ -53,8 +53,12 @@ files:
53
53
  - lib/carson/runtime.rb
54
54
  - lib/carson/runtime/audit.rb
55
55
  - lib/carson/runtime/govern.rb
56
- - lib/carson/runtime/lint.rb
57
56
  - lib/carson/runtime/local.rb
57
+ - lib/carson/runtime/local/hooks.rb
58
+ - lib/carson/runtime/local/onboard.rb
59
+ - lib/carson/runtime/local/prune.rb
60
+ - lib/carson/runtime/local/sync.rb
61
+ - lib/carson/runtime/local/template.rb
58
62
  - lib/carson/runtime/review.rb
59
63
  - lib/carson/runtime/review/data_access.rb
60
64
  - lib/carson/runtime/review/gate_support.rb
@@ -1,154 +0,0 @@
1
- require "fileutils"
2
- require "open3"
3
- require "tmpdir"
4
-
5
- module Carson
6
- class Runtime
7
- module Lint
8
- # Distributes lint policy files from a central source into the governed repository.
9
- # Target: <repo>/.github/linters/ (MegaLinter auto-discovers here).
10
- def lint_setup!( source:, ref: "main", force: false, **_ )
11
- puts_verbose ""
12
- puts_verbose "[Lint Policy]"
13
- source_text = source.to_s.strip
14
- if source_text.empty?
15
- puts_line "ERROR: lint policy requires --source <path-or-git-url>."
16
- return EXIT_ERROR
17
- end
18
-
19
- ref_text = ref.to_s.strip
20
- ref_text = "main" if ref_text.empty?
21
- source_dir, cleanup = lint_setup_source_directory( source: source_text, ref: ref_text )
22
- begin
23
- target_dir = repo_linters_dir
24
- copy_result = copy_lint_policy_files(
25
- source_dir: source_dir,
26
- target_dir: target_dir,
27
- force: force
28
- )
29
- puts_verbose "lint_policy_source: #{source_text}"
30
- puts_verbose "lint_policy_ref: #{ref_text}" if lint_source_git_url?( source: source_text )
31
- puts_verbose "lint_policy_target: #{target_dir}"
32
- puts_verbose "lint_policy_created: #{copy_result.fetch( :created )}"
33
- puts_verbose "lint_policy_updated: #{copy_result.fetch( :updated )}"
34
- puts_verbose "lint_policy_skipped: #{copy_result.fetch( :skipped )}"
35
-
36
- puts_line "OK: lint policy synced to .github/linters/ (#{copy_result.fetch( :created )} created, #{copy_result.fetch( :updated )} updated)."
37
- EXIT_OK
38
- ensure
39
- cleanup&.call
40
- end
41
- rescue StandardError => e
42
- puts_line "ERROR: lint policy failed (#{e.message})"
43
- EXIT_ERROR
44
- end
45
-
46
- private
47
- def lint_setup_source_directory( source:, ref: )
48
- if lint_source_git_url?( source: source )
49
- return lint_setup_clone_source( source: source, ref: ref )
50
- end
51
-
52
- expanded_source = File.expand_path( source )
53
- raise "source path does not exist: #{expanded_source}" unless Dir.exist?( expanded_source )
54
- [ expanded_source, nil ]
55
- end
56
-
57
- def lint_source_git_url?( source: )
58
- text = source.to_s.strip
59
- text.start_with?( "https://", "http://", "ssh://", "git@", "file://" )
60
- end
61
-
62
- def lint_setup_clone_source( source:, ref: )
63
- cache_root = cache_workspace_root
64
- FileUtils.mkdir_p( cache_root )
65
- work_dir = Dir.mktmpdir( "carson-lint-setup-", cache_root )
66
- checkout_dir = File.join( work_dir, "source" )
67
- clone_source = authenticated_lint_source( source: source )
68
- stdout_text, stderr_text, status = Open3.capture3(
69
- "git", "clone", "--depth", "1", "--branch", ref, clone_source, checkout_dir
70
- )
71
- unless status.success?
72
- error_text = [ stderr_text.to_s.strip, stdout_text.to_s.strip ].reject( &:empty? ).join( " | " )
73
- error_text = "git clone failed" if error_text.empty?
74
- raise "unable to clone lint source #{safe_lint_source( source: source )} (#{error_text})"
75
- end
76
- [ checkout_dir, -> { FileUtils.rm_rf( work_dir ) } ]
77
- end
78
-
79
- def authenticated_lint_source( source: )
80
- token = ENV.fetch( "CARSON_READ_TOKEN", "" ).to_s.strip
81
- return source if token.empty?
82
-
83
- return source unless source.start_with?( "https://github.com/", "http://github.com/", "git@github.com:" )
84
-
85
- if source.start_with?( "git@github.com:" )
86
- path = source.sub( "git@github.com:", "" )
87
- return "https://x-access-token:#{token}@github.com/#{path}"
88
- end
89
-
90
- source.sub( %r{\Ahttps?://github\.com/}, "https://x-access-token:#{token}@github.com/" )
91
- end
92
-
93
- def safe_lint_source( source: )
94
- source.to_s.gsub( %r{https://[^@]+@}, "https://***@" )
95
- end
96
-
97
- def cache_workspace_root
98
- home = ENV.fetch( "HOME", "" ).to_s.strip
99
- if home.start_with?( "/" )
100
- path = File.join( home, ".carson", "cache" )
101
- FileUtils.mkdir_p( path )
102
- return path
103
- end
104
- "/tmp/carson"
105
- rescue StandardError
106
- "/tmp/carson"
107
- end
108
-
109
- # Lint configs live inside the governed repository for MegaLinter.
110
- def repo_linters_dir
111
- File.join( repo_root, ".github", "linters" )
112
- end
113
-
114
- def copy_lint_policy_files( source_dir:, target_dir:, force: )
115
- FileUtils.mkdir_p( target_dir )
116
- created = 0
117
- updated = 0
118
- skipped = 0
119
- Dir.glob( "**/*", File::FNM_DOTMATCH, base: source_dir ).sort.each do |relative|
120
- next if [ ".", ".." ].include?( relative )
121
- next if relative.start_with?( ".git/" ) || relative == ".git"
122
- source_path = File.join( source_dir, relative )
123
- target_path = File.join( target_dir, relative )
124
- if File.directory?( source_path )
125
- FileUtils.mkdir_p( target_path )
126
- next
127
- end
128
- next unless File.file?( source_path )
129
-
130
- if File.exist?( target_path ) && !force
131
- skipped += 1
132
- next
133
- end
134
- target_exists = File.exist?( target_path )
135
- FileUtils.mkdir_p( File.dirname( target_path ) )
136
- FileUtils.cp( source_path, target_path )
137
- FileUtils.chmod( File.stat( source_path ).mode & 0o777, target_path )
138
- if target_exists
139
- updated += 1
140
- else
141
- created += 1
142
- end
143
- end
144
- {
145
- created: created,
146
- updated: updated,
147
- skipped: skipped
148
- }
149
- end
150
- end
151
-
152
- include Lint
153
- end
154
- end