carson 1.0.1 → 2.7.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.
@@ -131,7 +131,7 @@ module Carson
131
131
  end
132
132
 
133
133
  # Installs required hook files and enforces repository hook path.
134
- def hook!
134
+ def prepare!
135
135
  fingerprint_status = block_if_outsider_fingerprints!
136
136
  return fingerprint_status unless fingerprint_status.nil?
137
137
 
@@ -156,23 +156,24 @@ module Carson
156
156
  puts_line "hook_written: #{relative_path( target_path )}"
157
157
  end
158
158
  git_system!( "config", "core.hooksPath", hooks_dir )
159
+ File.write( File.join( hooks_dir, "workflow_style" ), config.workflow_style )
159
160
  puts_line "configured_hooks_path: #{hooks_dir}"
160
- check!
161
+ inspect!
161
162
  end
162
163
 
163
- # One-command initialisation for new repositories: align remote naming, install hooks,
164
+ # One-command onboarding for new repositories: align remote naming, install hooks,
164
165
  # apply templates, and produce a first audit report.
165
- def init!
166
+ def onboard!
166
167
  fingerprint_status = block_if_outsider_fingerprints!
167
168
  return fingerprint_status unless fingerprint_status.nil?
168
169
 
169
- print_header "Init"
170
+ print_header "Onboard"
170
171
  unless inside_git_work_tree?
171
172
  puts_line "ERROR: #{repo_root} is not a git repository."
172
173
  return EXIT_ERROR
173
174
  end
174
175
  align_remote_name_for_carson!
175
- hook_status = hook!
176
+ hook_status = prepare!
176
177
  return hook_status unless hook_status == EXIT_OK
177
178
 
178
179
  template_status = template_apply!
@@ -180,9 +181,35 @@ module Carson
180
181
 
181
182
  audit_status = audit!
182
183
  if audit_status == EXIT_OK
183
- puts_line "OK: Carson initialisation completed for #{repo_root}."
184
+ puts_line "OK: Carson onboard completed for #{repo_root}."
184
185
  elsif audit_status == EXIT_BLOCK
185
- puts_line "BLOCK: Carson initialisation completed with policy blocks; resolve and rerun carson audit."
186
+ puts_line "BLOCK: Carson onboard completed with policy blocks; resolve and rerun carson audit."
187
+ end
188
+ print_onboarding_guidance
189
+ audit_status
190
+ end
191
+
192
+ # Re-applies hooks, templates, and audit after upgrading Carson.
193
+ def refresh!
194
+ fingerprint_status = block_if_outsider_fingerprints!
195
+ return fingerprint_status unless fingerprint_status.nil?
196
+
197
+ print_header "Refresh"
198
+ unless inside_git_work_tree?
199
+ puts_line "ERROR: #{repo_root} is not a git repository."
200
+ return EXIT_ERROR
201
+ end
202
+ hook_status = prepare!
203
+ return hook_status unless hook_status == EXIT_OK
204
+
205
+ template_status = template_apply!
206
+ return template_status unless template_status == EXIT_OK
207
+
208
+ audit_status = audit!
209
+ if audit_status == EXIT_OK
210
+ puts_line "OK: Carson refresh completed for #{repo_root}."
211
+ elsif audit_status == EXIT_BLOCK
212
+ puts_line "BLOCK: Carson refresh completed with policy blocks; resolve and rerun carson audit."
186
213
  end
187
214
  audit_status
188
215
  end
@@ -217,11 +244,11 @@ module Carson
217
244
  end
218
245
 
219
246
  # Strict hook health check used by humans, hooks, and CI paths.
220
- def check!
247
+ def inspect!
221
248
  fingerprint_status = block_if_outsider_fingerprints!
222
249
  return fingerprint_status unless fingerprint_status.nil?
223
250
 
224
- print_header "Hooks Check"
251
+ print_header "Inspect"
225
252
  ok = hooks_health_report( strict: true )
226
253
  puts_line( ok ? "status: ok" : "status: block" )
227
254
  ok ? EXIT_OK : EXIT_BLOCK
@@ -309,7 +336,7 @@ module Carson
309
336
 
310
337
  # Canonical hook template location inside Carson repository.
311
338
  def hook_template_path( hook_name: )
312
- File.join( tool_root, "assets", "hooks", hook_name )
339
+ File.join( tool_root, "hooks", hook_name )
313
340
  end
314
341
 
315
342
  # Reports full hook health and can enforce stricter action messaging in `check`.
@@ -374,7 +401,7 @@ module Carson
374
401
  puts_line "ACTION: hooks path mismatch (configured=#{configured_text}, expected=#{expected})."
375
402
  end
376
403
  end
377
- message = strict ? "ACTION: run carson hook to align hooks with Carson #{Carson::VERSION}." : "ACTION: run carson hook to enforce local main protections."
404
+ message = strict ? "ACTION: run carson prepare to align hooks with Carson #{Carson::VERSION}." : "ACTION: run carson prepare to enforce local main protections."
378
405
  puts_line message
379
406
  end
380
407
 
@@ -649,6 +676,18 @@ module Carson
649
676
  puts_line "WARN: no #{config.git_remote} or origin remote configured; continue with local baseline only."
650
677
  end
651
678
 
679
+
680
+ def print_onboarding_guidance
681
+ puts_line ""
682
+ puts_line "Carson is ready. Current workflow: #{config.workflow_style}"
683
+ puts_line ""
684
+ puts_line "Customise in ~/.carson/config.json:"
685
+ puts_line " { \"workflow\": { \"style\": \"branch\" } } — enforce PR-only merges"
686
+ puts_line " { \"workflow\": { \"style\": \"trunk\" } } — allow direct main commits (default)"
687
+ puts_line ""
688
+ puts_line "Run carson refresh after changing config."
689
+ end
690
+
652
691
  # Uses `git remote get-url` as existence check to avoid parsing remote lists.
653
692
  def git_remote_exists?( remote_name: )
654
693
  _, _, success, = git_run( "remote", "get-url", remote_name.to_s )
@@ -4,10 +4,16 @@ module Carson
4
4
  module GateSupport
5
5
  private
6
6
 
7
- def wait_for_review_warmup
7
+ def wait_for_review_warmup( owner:, repo:, pr_number: )
8
8
  return unless config.review_wait_seconds.positive?
9
+ quick = review_gate_snapshot( owner: owner, repo: repo, pr_number: pr_number )
10
+ if quick[ :unresolved_threads ].empty? && quick[ :unacknowledged_actionable ].empty?
11
+ puts_line "warmup_skip: all threads resolved"
12
+ return quick
13
+ end
9
14
  puts_line "warmup_wait_seconds: #{config.review_wait_seconds}"
10
15
  sleep config.review_wait_seconds
16
+ nil
11
17
  end
12
18
 
13
19
  # Poll delay between consecutive snapshot reads during convergence checks.
@@ -60,6 +66,10 @@ module Carson
60
66
  }
61
67
  end
62
68
 
69
+ def bot_username?( author: )
70
+ config.review_bot_usernames.any? { |bot| bot.downcase == author.to_s.downcase }
71
+ end
72
+
63
73
  def unresolved_thread_entries( details: )
64
74
  Array( details.fetch( :review_threads ) ).each_with_index.map do |thread, index|
65
75
  next if thread.fetch( :is_resolved )
@@ -67,6 +77,7 @@ module Carson
67
77
  next if thread.fetch( :is_outdated )
68
78
  comments = thread.fetch( :comments )
69
79
  first_comment = comments.first || {}
80
+ next if bot_username?( author: first_comment.fetch( :author, "" ) )
70
81
  latest_time = comments.map { |entry| entry.fetch( :created_at ) }.max.to_s
71
82
  {
72
83
  url: blank_to( value: first_comment.fetch( :url, "" ), default: "#{details.fetch( :url )}#thread-#{index + 1}" ),
@@ -83,6 +94,7 @@ module Carson
83
94
  items = []
84
95
  Array( details.fetch( :comments ) ).each do |comment|
85
96
  next if comment.fetch( :author ) == pr_author
97
+ next if bot_username?( author: comment.fetch( :author ) )
86
98
  next if disposition_prefixed?( text: comment.fetch( :body ) )
87
99
  hits = matched_risk_keywords( text: comment.fetch( :body ) )
88
100
  next if hits.empty?
@@ -96,6 +108,7 @@ module Carson
96
108
  end
97
109
  Array( details.fetch( :reviews ) ).each do |review|
98
110
  next if review.fetch( :author ) == pr_author
111
+ next if bot_username?( author: review.fetch( :author ) )
99
112
  next if disposition_prefixed?( text: review.fetch( :body ) )
100
113
  hits = matched_risk_keywords( text: review.fetch( :body ) )
101
114
  changes_requested = review.fetch( :state ) == "CHANGES_REQUESTED"
@@ -56,10 +56,10 @@ module Carson
56
56
  return EXIT_BLOCK
57
57
  end
58
58
 
59
- wait_for_review_warmup
59
+ pre_snapshot = wait_for_review_warmup( owner: owner, repo: repo, pr_number: pr_summary.fetch( :number ) )
60
60
  converged = false
61
- last_snapshot = nil
62
- last_signature = nil
61
+ last_snapshot = pre_snapshot
62
+ last_signature = pre_snapshot.nil? ? nil : review_gate_signature( snapshot: pre_snapshot )
63
63
  poll_attempts = 0
64
64
 
65
65
  config.review_max_polls.times do |index|
@@ -3,6 +3,7 @@
3
3
  # adapter invocation, and report-location policy.
4
4
  require "fileutils"
5
5
  require "json"
6
+ require "open3"
6
7
  require "time"
7
8
 
8
9
  module Carson
@@ -58,8 +59,13 @@ module Carson
58
59
  end
59
60
 
60
61
  # Single output funnel to keep messaging style consistent.
62
+ # Prefixes non-empty lines with the Carson badge (⧓).
61
63
  def puts_line( message )
62
- out.puts message
64
+ if message.to_s.strip.empty?
65
+ out.puts ""
66
+ else
67
+ out.puts "#{BADGE} #{message}"
68
+ end
63
69
  end
64
70
 
65
71
  # Converts absolute paths into repo-relative output paths.
@@ -76,12 +82,12 @@ module Carson
76
82
  end
77
83
 
78
84
  # Resolves report output precedence:
79
- # 1) ~/.cache/carson when HOME is an absolute path
85
+ # 1) ~/.carson/cache when HOME is an absolute path
80
86
  # 2) TMPDIR/carson when HOME is invalid and TMPDIR is absolute
81
87
  # 3) /tmp/carson as final safety fallback
82
88
  def report_dir_path
83
89
  home = ENV.fetch( "HOME", "" ).to_s
84
- return File.join( home, ".cache", "carson" ) if absolute_env_path?( path: home )
90
+ return File.join( home, ".carson", "cache" ) if absolute_env_path?( path: home )
85
91
 
86
92
  tmpdir = ENV.fetch( "TMPDIR", "" ).to_s
87
93
  return File.join( tmpdir, "carson" ) if absolute_env_path?( path: tmpdir )
@@ -180,3 +186,4 @@ require_relative "runtime/local"
180
186
  require_relative "runtime/lint"
181
187
  require_relative "runtime/audit"
182
188
  require_relative "runtime/review"
189
+ require_relative "runtime/govern"
data/lib/carson.rb CHANGED
@@ -1,6 +1,15 @@
1
1
  require_relative "carson/version"
2
+
3
+ module Carson
4
+ BADGE = "\u29D3".freeze # ⧓ BLACK BOWTIE (U+29D3)
5
+ end
6
+
2
7
  require_relative "carson/config"
3
8
  require_relative "carson/adapters/git"
4
9
  require_relative "carson/adapters/github"
10
+ require_relative "carson/adapters/agent"
11
+ require_relative "carson/adapters/prompt"
12
+ require_relative "carson/adapters/codex"
13
+ require_relative "carson/adapters/claude"
5
14
  require_relative "carson/runtime"
6
15
  require_relative "carson/cli"
@@ -0,0 +1 @@
1
+ Read `.github/carson-instructions.md` for repository governance rules enforced by Carson.
@@ -0,0 +1 @@
1
+ Read `.github/carson-instructions.md` for repository governance rules enforced by Carson.
@@ -0,0 +1,12 @@
1
+ ## Shared Governance Baseline
2
+
3
+ - GitHub rulesets and required checks are merge authority.
4
+ - Carson runs as an outsider runtime for hook health, main sync, scope integrity, and gh visibility.
5
+ - Before commit and before push, run `carson audit`.
6
+ - At session start and again immediately before merge recommendation, run `gh pr list --state open --limit 50` and re-confirm active PR priorities.
7
+ - Before merge recommendation, run `carson review gate`; it enforces warm-up wait, unresolved-thread convergence, and `Disposition:` dispositions for actionable top-level findings.
8
+ - Actionable findings are unresolved review threads, any non-author `CHANGES_REQUESTED` review, or non-author comments/reviews with risk keywords (`bug`, `security`, `incorrect`, `block`, `fail`, `regression`).
9
+ - `Disposition:` dispositions must include one token (`accepted`, `rejected`, `deferred`) and the target review URL.
10
+ - Scheduled governance runs `carson review sweep` every 8 hours to track late actionable review activity on recent open/closed PRs.
11
+ - Do not treat green checks or `mergeStateStatus: CLEAN` as sufficient if unresolved review threads remain.
12
+ - Never suggest destructive operations on protected refs (`main`/`master`, local or remote).
@@ -1,12 +1 @@
1
- ## Shared Governance Baseline
2
-
3
- - GitHub rulesets and required checks are merge authority.
4
- - Carson runs as an outsider runtime for hook health, main sync, scope integrity, and gh visibility.
5
- - Before commit and before push, run `carson audit`.
6
- - At session start and again immediately before merge recommendation, run `gh pr list --state open --limit 50` and re-confirm active PR priorities.
7
- - Before merge recommendation, run `carson review gate`; it enforces warm-up wait, unresolved-thread convergence, and `Disposition:` dispositions for actionable top-level findings.
8
- - Actionable findings are unresolved review threads, any non-author `CHANGES_REQUESTED` review, or non-author comments/reviews with risk keywords (`bug`, `security`, `incorrect`, `block`, `fail`, `regression`).
9
- - `Disposition:` dispositions must include one token (`accepted`, `rejected`, `deferred`) and the target review URL.
10
- - Scheduled governance runs `carson review sweep` every 8 hours to track late actionable review activity on recent open/closed PRs.
11
- - Do not treat green checks or `mergeStateStatus: CLEAN` as sufficient if unresolved review threads remain.
12
- - Never suggest destructive operations on protected refs (`main`/`master`, local or remote).
1
+ Read `.github/carson-instructions.md` for repository governance rules enforced by Carson.
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: 1.0.1
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang
@@ -26,21 +26,28 @@ files:
26
26
  - MANUAL.md
27
27
  - README.md
28
28
  - RELEASE.md
29
+ - SKILL.md
29
30
  - VERSION
30
- - assets/hooks/pre-commit
31
- - assets/hooks/pre-merge-commit
32
- - assets/hooks/pre-push
33
- - assets/hooks/prepare-commit-msg
34
31
  - carson.gemspec
35
32
  - exe/carson
33
+ - hooks/pre-commit
34
+ - hooks/pre-merge-commit
35
+ - hooks/pre-push
36
+ - hooks/prepare-commit-msg
37
+ - icon.svg
36
38
  - lib/carson.rb
39
+ - lib/carson/adapters/agent.rb
40
+ - lib/carson/adapters/claude.rb
41
+ - lib/carson/adapters/codex.rb
37
42
  - lib/carson/adapters/git.rb
38
43
  - lib/carson/adapters/github.rb
44
+ - lib/carson/adapters/prompt.rb
39
45
  - lib/carson/cli.rb
40
46
  - lib/carson/config.rb
41
47
  - lib/carson/policy/ruby/lint.rb
42
48
  - lib/carson/runtime.rb
43
49
  - lib/carson/runtime/audit.rb
50
+ - lib/carson/runtime/govern.rb
44
51
  - lib/carson/runtime/lint.rb
45
52
  - lib/carson/runtime/local.rb
46
53
  - lib/carson/runtime/review.rb
@@ -50,6 +57,9 @@ files:
50
57
  - lib/carson/runtime/review/sweep_support.rb
51
58
  - lib/carson/runtime/review/utility.rb
52
59
  - lib/carson/version.rb
60
+ - templates/.github/AGENTS.md
61
+ - templates/.github/CLAUDE.md
62
+ - templates/.github/carson-instructions.md
53
63
  - templates/.github/copilot-instructions.md
54
64
  - templates/.github/pull_request_template.md
55
65
  homepage: https://github.com/wanghailei/carson