carson 1.0.0 → 2.6.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.
- checksums.yaml +4 -4
- data/.github/copilot-instructions.md +1 -12
- data/.github/workflows/carson_policy.yml +1 -1
- data/API.md +50 -13
- data/MANUAL.md +140 -65
- data/README.md +140 -33
- data/RELEASE.md +350 -6
- data/SKILL.md +102 -0
- data/VERSION +1 -1
- data/carson.gemspec +3 -1
- data/{assets/hooks → hooks}/pre-commit +1 -1
- data/{assets/hooks → hooks}/pre-merge-commit +4 -0
- data/{assets/hooks → hooks}/pre-push +4 -0
- data/{assets/hooks → hooks}/prepare-commit-msg +4 -0
- data/icon.svg +651 -0
- data/lib/carson/adapters/agent.rb +15 -0
- data/lib/carson/adapters/claude.rb +45 -0
- data/lib/carson/adapters/codex.rb +45 -0
- data/lib/carson/adapters/prompt.rb +60 -0
- data/lib/carson/cli.rb +65 -20
- data/lib/carson/config.rb +100 -14
- data/lib/carson/policy/ruby/lint.rb +1 -1
- data/lib/carson/runtime/audit.rb +33 -10
- data/lib/carson/runtime/govern.rb +641 -0
- data/lib/carson/runtime/lint.rb +3 -3
- data/lib/carson/runtime/local.rb +51 -12
- data/lib/carson/runtime/review/gate_support.rb +14 -1
- data/lib/carson/runtime/review.rb +3 -3
- data/lib/carson/runtime.rb +10 -3
- data/lib/carson.rb +9 -0
- data/templates/.github/AGENTS.md +1 -0
- data/templates/.github/CLAUDE.md +1 -0
- data/templates/.github/carson-instructions.md +12 -0
- data/templates/.github/copilot-instructions.md +1 -12
- metadata +15 -5
data/lib/carson/runtime/local.rb
CHANGED
|
@@ -131,7 +131,7 @@ module Carson
|
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
# Installs required hook files and enforces repository hook path.
|
|
134
|
-
def
|
|
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
|
-
|
|
161
|
+
inspect!
|
|
161
162
|
end
|
|
162
163
|
|
|
163
|
-
# One-command
|
|
164
|
+
# One-command onboarding for new repositories: align remote naming, install hooks,
|
|
164
165
|
# apply templates, and produce a first audit report.
|
|
165
|
-
def
|
|
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 "
|
|
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 =
|
|
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
|
|
184
|
+
puts_line "OK: Carson onboard completed for #{repo_root}."
|
|
184
185
|
elsif audit_status == EXIT_BLOCK
|
|
185
|
-
puts_line "BLOCK: Carson
|
|
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
|
|
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 "
|
|
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, "
|
|
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
|
|
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 =
|
|
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|
|
data/lib/carson/runtime.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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, ".
|
|
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
|
-
|
|
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:
|
|
4
|
+
version: 2.6.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
|