carson 3.19.0 → 3.21.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/README.md +11 -3
- data/RELEASE.md +25 -0
- data/VERSION +1 -1
- data/exe/carson +3 -3
- data/hooks/command-guard +56 -0
- data/hooks/pre-push +37 -1
- data/lib/carson/adapters/agent.rb +1 -0
- data/lib/carson/adapters/claude.rb +2 -0
- data/lib/carson/adapters/codex.rb +2 -0
- data/lib/carson/adapters/git.rb +2 -0
- data/lib/carson/adapters/github.rb +2 -0
- data/lib/carson/adapters/prompt.rb +2 -0
- data/lib/carson/cli.rb +415 -414
- data/lib/carson/config.rb +4 -3
- data/lib/carson/runtime/audit.rb +84 -84
- data/lib/carson/runtime/deliver.rb +27 -24
- data/lib/carson/runtime/govern.rb +29 -29
- data/lib/carson/runtime/housekeep.rb +15 -15
- data/lib/carson/runtime/local/hooks.rb +20 -0
- data/lib/carson/runtime/local/onboard.rb +17 -17
- data/lib/carson/runtime/local/prune.rb +13 -13
- data/lib/carson/runtime/local/sync.rb +6 -6
- data/lib/carson/runtime/local/template.rb +26 -25
- data/lib/carson/runtime/local/worktree.rb +76 -33
- data/lib/carson/runtime/local.rb +1 -0
- data/lib/carson/runtime/repos.rb +1 -1
- data/lib/carson/runtime/review/data_access.rb +1 -0
- data/lib/carson/runtime/review/gate_support.rb +15 -14
- data/lib/carson/runtime/review/query_text.rb +1 -0
- data/lib/carson/runtime/review/sweep_support.rb +5 -4
- data/lib/carson/runtime/review/utility.rb +2 -1
- data/lib/carson/runtime/review.rb +10 -8
- data/lib/carson/runtime/setup.rb +12 -10
- data/lib/carson/runtime/status.rb +20 -20
- data/lib/carson/runtime.rb +39 -25
- data/lib/carson/version.rb +1 -0
- data/lib/carson.rb +1 -0
- data/templates/.github/carson.md +7 -4
- metadata +2 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# Review sweep: late-event detection, tracking issue management, and sweep reporting.
|
|
1
2
|
module Carson
|
|
2
3
|
class Runtime
|
|
3
4
|
module Review
|
|
@@ -59,7 +60,7 @@ module Carson
|
|
|
59
60
|
)
|
|
60
61
|
end
|
|
61
62
|
|
|
62
|
-
Array( details.fetch( :review_threads ) ).flat_map {
|
|
63
|
+
Array( details.fetch( :review_threads ) ).flat_map { it.fetch( :comments ) }.each do |comment|
|
|
63
64
|
next if comment.fetch( :author ) == pr_author
|
|
64
65
|
next if bot_username?( author: comment.fetch( :author ) )
|
|
65
66
|
hits = matched_risk_keywords( text: comment.fetch( :body ) )
|
|
@@ -152,7 +153,7 @@ module Carson
|
|
|
152
153
|
stdout_text, stderr_text, success, = gh_run( "issue", "list", "--repo", repo_slug, "--state", "all", "--limit", "100", "--json", "number,title,state,url,labels" )
|
|
153
154
|
raise gh_error_text( stdout_text: stdout_text, stderr_text: stderr_text, fallback: "unable to list issues for review sweep" ) unless success
|
|
154
155
|
issues = Array( JSON.parse( stdout_text ) )
|
|
155
|
-
node = issues.find {
|
|
156
|
+
node = issues.find { it[ "title" ].to_s == config.review_tracking_issue_title }
|
|
156
157
|
return nil if node.nil?
|
|
157
158
|
{
|
|
158
159
|
number: node[ "number" ],
|
|
@@ -207,8 +208,8 @@ module Carson
|
|
|
207
208
|
)
|
|
208
209
|
puts_verbose "review_sweep_report_markdown: #{markdown_path}"
|
|
209
210
|
puts_verbose "review_sweep_report_json: #{json_path}"
|
|
210
|
-
rescue StandardError =>
|
|
211
|
-
puts_verbose "review_sweep_report_write: SKIP (#{
|
|
211
|
+
rescue StandardError => exception
|
|
212
|
+
puts_verbose "review_sweep_report_write: SKIP (#{exception.message})"
|
|
212
213
|
end
|
|
213
214
|
|
|
214
215
|
# Human-readable scheduled sweep report.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# Review utilities: risk keyword matching, disposition parsing, URL extraction, and deduplication.
|
|
1
2
|
module Carson
|
|
2
3
|
class Runtime
|
|
3
4
|
module Review
|
|
@@ -24,7 +25,7 @@ module Carson
|
|
|
24
25
|
|
|
25
26
|
# GitHub URL extraction for mapping disposition acknowledgements to finding URLs.
|
|
26
27
|
def extract_github_urls( text: )
|
|
27
|
-
text.to_s.scan( %r{https://github\.com/[^\s\)\]]+} ).map {
|
|
28
|
+
text.to_s.scan( %r{https://github\.com/[^\s\)\]]+} ).map { it.sub( /[.,;:]+$/, "" ) }.uniq
|
|
28
29
|
end
|
|
29
30
|
|
|
30
31
|
# Parse RFC3339 timestamps and return nil on blank/invalid values.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# Implements the review gate (merge readiness) and sweep (late activity scan) workflows.
|
|
1
2
|
require_relative "review/query_text"
|
|
2
3
|
require_relative "review/data_access"
|
|
3
4
|
require_relative "review/gate_support"
|
|
@@ -6,6 +7,7 @@ require_relative "review/utility"
|
|
|
6
7
|
|
|
7
8
|
module Carson
|
|
8
9
|
class Runtime
|
|
10
|
+
# PR review gate and sweep workflow.
|
|
9
11
|
module Review
|
|
10
12
|
include QueryText
|
|
11
13
|
include DataAccess
|
|
@@ -123,11 +125,11 @@ module Carson
|
|
|
123
125
|
end
|
|
124
126
|
block_reasons.each { |reason| puts_line "BLOCK: #{reason}" }
|
|
125
127
|
EXIT_BLOCK
|
|
126
|
-
rescue JSON::ParserError =>
|
|
127
|
-
puts_line "ERROR: invalid gh JSON response (#{
|
|
128
|
+
rescue JSON::ParserError => exception
|
|
129
|
+
puts_line "ERROR: invalid gh JSON response (#{exception.message})."
|
|
128
130
|
EXIT_ERROR
|
|
129
|
-
rescue StandardError =>
|
|
130
|
-
puts_line "ERROR: #{
|
|
131
|
+
rescue StandardError => exception
|
|
132
|
+
puts_line "ERROR: #{exception.message}"
|
|
131
133
|
EXIT_ERROR
|
|
132
134
|
end
|
|
133
135
|
|
|
@@ -176,11 +178,11 @@ module Carson
|
|
|
176
178
|
end
|
|
177
179
|
puts_line "BLOCK: actionable late review activity detected."
|
|
178
180
|
EXIT_BLOCK
|
|
179
|
-
rescue JSON::ParserError =>
|
|
180
|
-
puts_line "ERROR: invalid gh JSON response (#{
|
|
181
|
+
rescue JSON::ParserError => exception
|
|
182
|
+
puts_line "ERROR: invalid gh JSON response (#{exception.message})."
|
|
181
183
|
EXIT_ERROR
|
|
182
|
-
rescue StandardError =>
|
|
183
|
-
puts_line "ERROR: #{
|
|
184
|
+
rescue StandardError => exception
|
|
185
|
+
puts_line "ERROR: #{exception.message}"
|
|
184
186
|
EXIT_ERROR
|
|
185
187
|
end
|
|
186
188
|
end
|
data/lib/carson/runtime/setup.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# Handles first-time setup, onboard, offboard, refresh, and config persistence.
|
|
1
2
|
require "set"
|
|
2
3
|
require "uri"
|
|
3
4
|
|
|
4
5
|
module Carson
|
|
5
6
|
class Runtime
|
|
7
|
+
# First-time setup, onboard, offboard, and refresh operations.
|
|
6
8
|
module Setup
|
|
7
9
|
WELL_KNOWN_REMOTES = %w[origin github upstream].freeze
|
|
8
10
|
|
|
@@ -137,7 +139,7 @@ module Carson
|
|
|
137
139
|
{ label: "branch — enforce PR-only merges (default)", value: "branch" },
|
|
138
140
|
{ label: "trunk — commit directly to main", value: "trunk" }
|
|
139
141
|
]
|
|
140
|
-
default_index = options.index {
|
|
142
|
+
default_index = options.index { it.fetch( :value ) == current } || 0
|
|
141
143
|
prompt_choice( options: options, default: default_index )
|
|
142
144
|
end
|
|
143
145
|
|
|
@@ -151,7 +153,7 @@ module Carson
|
|
|
151
153
|
{ label: "rebase — linear history, individual commits", value: "rebase" },
|
|
152
154
|
{ label: "merge — merge commits", value: "merge" }
|
|
153
155
|
]
|
|
154
|
-
default_index = options.index {
|
|
156
|
+
default_index = options.index { it.fetch( :value ) == current } || 0
|
|
155
157
|
prompt_choice( options: options, default: default_index )
|
|
156
158
|
end
|
|
157
159
|
|
|
@@ -173,8 +175,8 @@ module Carson
|
|
|
173
175
|
options.each_with_index do |option, index|
|
|
174
176
|
puts_line " #{index + 1}) #{option.fetch( :label )}"
|
|
175
177
|
end
|
|
176
|
-
|
|
177
|
-
|
|
178
|
+
output.print "#{BADGE} Choice [#{default + 1}]: "
|
|
179
|
+
output.flush
|
|
178
180
|
raw = self.in.gets
|
|
179
181
|
return options[ default ].fetch( :value ) if raw.nil?
|
|
180
182
|
|
|
@@ -190,8 +192,8 @@ module Carson
|
|
|
190
192
|
end
|
|
191
193
|
|
|
192
194
|
def prompt_custom_value( label: )
|
|
193
|
-
|
|
194
|
-
|
|
195
|
+
output.print "#{BADGE} #{label}: "
|
|
196
|
+
output.flush
|
|
195
197
|
raw = self.in.gets
|
|
196
198
|
return nil if raw.nil?
|
|
197
199
|
|
|
@@ -219,7 +221,7 @@ module Carson
|
|
|
219
221
|
others << entry
|
|
220
222
|
end
|
|
221
223
|
end
|
|
222
|
-
well_known.sort_by {
|
|
224
|
+
well_known.sort_by { WELL_KNOWN_REMOTES.index( it.fetch( :name ) ) || 999 } + others.sort_by { it.fetch( :name ) }
|
|
223
225
|
end
|
|
224
226
|
|
|
225
227
|
# Normalises a remote URL so SSH and HTTPS variants of the same host/path compare equal.
|
|
@@ -295,7 +297,7 @@ module Carson
|
|
|
295
297
|
|
|
296
298
|
def detect_git_remote
|
|
297
299
|
remotes = list_git_remotes
|
|
298
|
-
remote_names = remotes.map {
|
|
300
|
+
remote_names = remotes.map { it.fetch( :name ) }
|
|
299
301
|
return nil if remote_names.empty?
|
|
300
302
|
|
|
301
303
|
return config.git_remote if remote_names.include?( config.git_remote )
|
|
@@ -383,8 +385,8 @@ module Carson
|
|
|
383
385
|
# Reusable Y/n prompt following existing prompt_choice conventions.
|
|
384
386
|
def prompt_yes_no( default: true )
|
|
385
387
|
hint = default ? "Y/n" : "y/N"
|
|
386
|
-
|
|
387
|
-
|
|
388
|
+
output.print "#{BADGE} [#{hint}]: "
|
|
389
|
+
output.flush
|
|
388
390
|
raw = self.in.gets
|
|
389
391
|
return default if raw.nil?
|
|
390
392
|
|
|
@@ -9,7 +9,7 @@ module Carson
|
|
|
9
9
|
data = gather_status
|
|
10
10
|
|
|
11
11
|
if json_output
|
|
12
|
-
|
|
12
|
+
output.puts JSON.pretty_generate( data )
|
|
13
13
|
else
|
|
14
14
|
print_status( data: data )
|
|
15
15
|
end
|
|
@@ -35,14 +35,14 @@ module Carson
|
|
|
35
35
|
next
|
|
36
36
|
end
|
|
37
37
|
begin
|
|
38
|
-
|
|
39
|
-
data =
|
|
38
|
+
scoped_runtime = build_scoped_runtime( repo_path: repo_path )
|
|
39
|
+
data = scoped_runtime.send( :gather_status )
|
|
40
40
|
results << { name: repo_name, status: "ok" }.merge( data )
|
|
41
|
-
rescue StandardError =>
|
|
42
|
-
results << { name: repo_name, status: "error", error:
|
|
41
|
+
rescue StandardError => exception
|
|
42
|
+
results << { name: repo_name, status: "error", error: exception.message }
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
|
-
|
|
45
|
+
output.puts JSON.pretty_generate( { command: "status", repos: results } )
|
|
46
46
|
return EXIT_OK
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -58,8 +58,8 @@ module Carson
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
begin
|
|
61
|
-
|
|
62
|
-
data =
|
|
61
|
+
scoped_runtime = build_scoped_runtime( repo_path: repo_path )
|
|
62
|
+
data = scoped_runtime.send( :gather_status )
|
|
63
63
|
branch = data.fetch( :branch )
|
|
64
64
|
dirty = branch.fetch( :dirty ) ? " (dirty)" : ""
|
|
65
65
|
worktrees = data.fetch( :worktrees )
|
|
@@ -72,9 +72,9 @@ module Carson
|
|
|
72
72
|
|
|
73
73
|
# Show pending operations for this repo.
|
|
74
74
|
repo_pending = status_pending_for_repo( all_pending: all_pending, repo_path: repo_path )
|
|
75
|
-
repo_pending.each { |
|
|
76
|
-
rescue StandardError =>
|
|
77
|
-
puts_line "#{repo_name}: FAIL (#{
|
|
75
|
+
repo_pending.each { |description| puts_line " pending: #{description}" }
|
|
76
|
+
rescue StandardError => exception
|
|
77
|
+
puts_line "#{repo_name}: FAIL (#{exception.message})"
|
|
78
78
|
end
|
|
79
79
|
end
|
|
80
80
|
|
|
@@ -159,14 +159,14 @@ module Carson
|
|
|
159
159
|
def gather_worktree_info
|
|
160
160
|
entries = worktree_list
|
|
161
161
|
|
|
162
|
-
# Filter
|
|
162
|
+
# Filter output the main worktree (the repository root itself).
|
|
163
163
|
# Use realpath for comparison — git returns canonical paths that may differ from repo_root.
|
|
164
164
|
canonical_root = realpath_safe( repo_root )
|
|
165
|
-
entries.reject { it.fetch( :path ) == canonical_root }.map do |
|
|
165
|
+
entries.reject { it.fetch( :path ) == canonical_root }.map do |worktree|
|
|
166
166
|
{
|
|
167
|
-
path:
|
|
168
|
-
name: File.basename(
|
|
169
|
-
branch:
|
|
167
|
+
path: worktree.fetch( :path ),
|
|
168
|
+
name: File.basename( worktree.fetch( :path ) ),
|
|
169
|
+
branch: worktree.fetch( :branch, nil )
|
|
170
170
|
}
|
|
171
171
|
end
|
|
172
172
|
end
|
|
@@ -222,7 +222,7 @@ module Carson
|
|
|
222
222
|
stdout, _, success, = git_run( "branch", "-vv" )
|
|
223
223
|
return { count: 0 } unless success
|
|
224
224
|
|
|
225
|
-
gone_branches = stdout.lines.select { |
|
|
225
|
+
gone_branches = stdout.lines.select { |line| line.include?( ": gone]" ) }
|
|
226
226
|
{ count: gone_branches.size }
|
|
227
227
|
end
|
|
228
228
|
|
|
@@ -252,9 +252,9 @@ module Carson
|
|
|
252
252
|
if worktrees.any?
|
|
253
253
|
puts_line ""
|
|
254
254
|
puts_line "Worktrees:"
|
|
255
|
-
worktrees.each do |
|
|
256
|
-
branch_label =
|
|
257
|
-
puts_line " #{
|
|
255
|
+
worktrees.each do |worktree|
|
|
256
|
+
branch_label = worktree.fetch( :branch ) || "(detached)"
|
|
257
|
+
puts_line " #{worktree.fetch( :name )} #{branch_label}"
|
|
258
258
|
end
|
|
259
259
|
end
|
|
260
260
|
|
data/lib/carson/runtime.rb
CHANGED
|
@@ -23,11 +23,11 @@ module Carson
|
|
|
23
23
|
DISPOSITION_TOKENS = %w[accepted rejected deferred].freeze
|
|
24
24
|
|
|
25
25
|
# Runtime wiring for repository context, tool paths, and output streams.
|
|
26
|
-
def initialize( repo_root:, tool_root:,
|
|
26
|
+
def initialize( repo_root:, tool_root:, output:, error:, in_stream: $stdin, verbose: false )
|
|
27
27
|
@repo_root = repo_root
|
|
28
28
|
@tool_root = tool_root
|
|
29
|
-
@
|
|
30
|
-
@
|
|
29
|
+
@output = output
|
|
30
|
+
@error = error
|
|
31
31
|
@in = in_stream
|
|
32
32
|
@verbose = verbose
|
|
33
33
|
@config = Config.load( repo_root: repo_root )
|
|
@@ -40,7 +40,7 @@ module Carson
|
|
|
40
40
|
|
|
41
41
|
private
|
|
42
42
|
|
|
43
|
-
attr_reader :repo_root, :tool_root, :
|
|
43
|
+
attr_reader :repo_root, :tool_root, :output, :error, :in, :config, :git_adapter, :github_adapter
|
|
44
44
|
|
|
45
45
|
# Returns true when full diagnostic output is enabled via --verbose.
|
|
46
46
|
def verbose?
|
|
@@ -55,12 +55,12 @@ module Carson
|
|
|
55
55
|
# Runs a block with all output captured (suppressed from the user).
|
|
56
56
|
# Returns the block's return value; output is silently discarded.
|
|
57
57
|
def with_captured_output
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
@
|
|
58
|
+
saved_output, saved_error = @output, @error
|
|
59
|
+
@output = StringIO.new
|
|
60
|
+
@error = StringIO.new
|
|
61
61
|
yield
|
|
62
62
|
ensure
|
|
63
|
-
@
|
|
63
|
+
@output, @error = saved_output, saved_error
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
# Returns true when the repository has at least one commit (HEAD exists).
|
|
@@ -95,9 +95,9 @@ module Carson
|
|
|
95
95
|
# Prefixes non-empty lines with the Carson badge (⧓).
|
|
96
96
|
def puts_line( message )
|
|
97
97
|
if message.to_s.strip.empty?
|
|
98
|
-
|
|
98
|
+
output.puts ""
|
|
99
99
|
else
|
|
100
|
-
|
|
100
|
+
output.puts "#{BADGE} #{message}"
|
|
101
101
|
end
|
|
102
102
|
end
|
|
103
103
|
|
|
@@ -160,6 +160,20 @@ module Carson
|
|
|
160
160
|
text.empty? ? default : text
|
|
161
161
|
end
|
|
162
162
|
|
|
163
|
+
# Temporarily sets an environment variable for the duration of the block.
|
|
164
|
+
# Restores the previous value (or deletes the key) when the block completes.
|
|
165
|
+
def with_env_var( key, value )
|
|
166
|
+
previous = ENV.key?( key ) ? ENV.fetch( key ) : nil
|
|
167
|
+
ENV[ key ] = value
|
|
168
|
+
yield
|
|
169
|
+
ensure
|
|
170
|
+
if previous.nil?
|
|
171
|
+
ENV.delete( key )
|
|
172
|
+
else
|
|
173
|
+
ENV[ key ] = previous
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
163
177
|
# Chooses best available error text from gh stderr/stdout.
|
|
164
178
|
def gh_error_text( stdout_text:, stderr_text:, fallback: )
|
|
165
179
|
combined = [ stderr_text.to_s.strip, stdout_text.to_s.strip ].reject( &:empty? ).join( " | " )
|
|
@@ -182,8 +196,8 @@ module Carson
|
|
|
182
196
|
# Runs git command, streams outputs, and raises on non-zero exit.
|
|
183
197
|
def git_system!( *args )
|
|
184
198
|
stdout_text, stderr_text, success, = git_run( *args )
|
|
185
|
-
|
|
186
|
-
|
|
199
|
+
output.print stdout_text unless stdout_text.empty?
|
|
200
|
+
error.print stderr_text unless stderr_text.empty?
|
|
187
201
|
raise "git #{args.join( ' ' )} failed" unless success
|
|
188
202
|
end
|
|
189
203
|
|
|
@@ -191,7 +205,7 @@ module Carson
|
|
|
191
205
|
def git_capture!( *args )
|
|
192
206
|
stdout_text, stderr_text, success, = git_run( *args )
|
|
193
207
|
unless success
|
|
194
|
-
|
|
208
|
+
error.print stderr_text unless stderr_text.empty?
|
|
195
209
|
raise "git #{args.join( ' ' )} failed"
|
|
196
210
|
end
|
|
197
211
|
stdout_text
|
|
@@ -234,9 +248,9 @@ module Carson
|
|
|
234
248
|
def save_batch_pending( data )
|
|
235
249
|
path = batch_pending_path
|
|
236
250
|
FileUtils.mkdir_p( File.dirname( path ) )
|
|
237
|
-
|
|
238
|
-
File.write(
|
|
239
|
-
File.rename(
|
|
251
|
+
temporary_path = "#{path}.tmp"
|
|
252
|
+
File.write( temporary_path, JSON.pretty_generate( data ) )
|
|
253
|
+
File.rename( temporary_path, path )
|
|
240
254
|
end
|
|
241
255
|
|
|
242
256
|
# Adds or updates an entry in the pending log, incrementing attempts.
|
|
@@ -293,10 +307,10 @@ module Carson
|
|
|
293
307
|
reasons = []
|
|
294
308
|
|
|
295
309
|
# Active worktrees beyond the main working tree.
|
|
296
|
-
|
|
297
|
-
worktrees =
|
|
298
|
-
main_root =
|
|
299
|
-
active = worktrees.reject { |
|
|
310
|
+
scoped_runtime = build_scoped_runtime( repo_path: repo_path )
|
|
311
|
+
worktrees = scoped_runtime.send( :worktree_list )
|
|
312
|
+
main_root = scoped_runtime.send( :realpath_safe, repo_path )
|
|
313
|
+
active = worktrees.reject { |worktree| worktree.fetch( :path ) == main_root }
|
|
300
314
|
if active.any?
|
|
301
315
|
reasons << "#{active.count} active worktree#{active.count == 1 ? '' : 's'}"
|
|
302
316
|
end
|
|
@@ -308,15 +322,15 @@ module Carson
|
|
|
308
322
|
end
|
|
309
323
|
|
|
310
324
|
{ safe: reasons.empty?, reasons: reasons }
|
|
311
|
-
rescue StandardError =>
|
|
312
|
-
{ safe: false, reasons: [
|
|
325
|
+
rescue StandardError => exception
|
|
326
|
+
{ safe: false, reasons: [ exception.message ] }
|
|
313
327
|
end
|
|
314
328
|
|
|
315
329
|
# Creates a scoped Runtime for a governed repo with captured output.
|
|
316
330
|
def build_scoped_runtime( repo_path: )
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
Runtime.new( repo_root: repo_path, tool_root: tool_root,
|
|
331
|
+
buffer = verbose? ? output : StringIO.new
|
|
332
|
+
error_buffer = verbose? ? error : StringIO.new
|
|
333
|
+
Runtime.new( repo_root: repo_path, tool_root: tool_root, output: buffer, error: error_buffer, verbose: verbose? )
|
|
320
334
|
end
|
|
321
335
|
end
|
|
322
336
|
end
|
data/lib/carson/version.rb
CHANGED
data/lib/carson.rb
CHANGED
data/templates/.github/carson.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
This repository is governed by [Carson](https://github.com/wanghailei/carson), an autonomous governance runtime. Carson lives on the maintainer's workstation, not inside this repository.
|
|
4
4
|
|
|
5
|
-
## What Carson Does Not Do
|
|
6
|
-
|
|
7
|
-
Carson has no `commit`, `push`, or `pr` commands. Use `git` and `gh` for those. Carson audits and governs; you execute.
|
|
8
|
-
|
|
9
5
|
## Commands
|
|
10
6
|
|
|
7
|
+
**Delivery:**
|
|
8
|
+
```bash
|
|
9
|
+
carson deliver # push branch, create PR
|
|
10
|
+
carson deliver --merge # push, create PR, merge if CI green and review clear
|
|
11
|
+
```
|
|
12
|
+
|
|
11
13
|
**Before committing:**
|
|
12
14
|
```bash
|
|
13
15
|
carson audit # full governance check — run before every commit
|
|
@@ -24,6 +26,7 @@ carson review gate # block until actionable review findings are resolved
|
|
|
24
26
|
```bash
|
|
25
27
|
carson sync # fast-forward local main from remote
|
|
26
28
|
carson prune # remove stale branches (safer than git branch -d on squash repos)
|
|
29
|
+
carson housekeep # sync + prune + sweep stale worktrees
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
## 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: 3.
|
|
4
|
+
version: 3.21.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Hailei Wang
|
|
@@ -36,6 +36,7 @@ files:
|
|
|
36
36
|
- VERSION
|
|
37
37
|
- carson.gemspec
|
|
38
38
|
- exe/carson
|
|
39
|
+
- hooks/command-guard
|
|
39
40
|
- hooks/pre-commit
|
|
40
41
|
- hooks/pre-merge-commit
|
|
41
42
|
- hooks/pre-push
|