carson 3.30.1 → 3.30.3
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/API.md +16 -27
- data/MANUAL.md +19 -30
- data/README.md +5 -5
- data/RELEASE.md +34 -0
- data/VERSION +1 -1
- data/carson.gemspec +1 -1
- data/lib/carson/cli.rb +298 -225
- data/lib/carson/runtime/audit.rb +0 -52
- data/lib/carson/runtime/deliver.rb +8 -9
- data/lib/carson/runtime/housekeep.rb +0 -78
- data/lib/carson/runtime/{repos.rb → list.rb} +4 -4
- data/lib/carson/runtime/local/hooks.rb +1 -1
- data/lib/carson/runtime/local/onboard.rb +0 -50
- data/lib/carson/runtime/local/sync.rb +1 -48
- data/lib/carson/runtime/local/template.rb +0 -47
- data/lib/carson/runtime/{govern.rb → receive.rb} +37 -46
- data/lib/carson/runtime/recover.rb +2 -2
- data/lib/carson/runtime/status.rb +1 -47
- data/lib/carson/runtime.rb +11 -7
- metadata +8 -8
- /data/{hooks → config/.github/hooks}/command-guard +0 -0
- /data/{hooks → config/.github/hooks}/pre-commit +0 -0
- /data/{hooks → config/.github/hooks}/pre-merge-commit +0 -0
- /data/{hooks → config/.github/hooks}/pre-push +0 -0
- /data/{hooks → config/.github/hooks}/prepare-commit-msg +0 -0
data/lib/carson/cli.rb
CHANGED
|
@@ -1,26 +1,50 @@
|
|
|
1
1
|
# Parses command-line arguments and dispatches to Runtime operations.
|
|
2
|
+
require "open3"
|
|
2
3
|
require "optparse"
|
|
3
4
|
|
|
4
5
|
module Carson
|
|
5
6
|
class CLI
|
|
7
|
+
PORTFOLIO_COMMANDS = %w[onboard offboard list refresh version].freeze
|
|
8
|
+
REPO_COMMANDS = %w[deliver receive sync status audit prune housekeep worktree abandon recover review template setup].freeze
|
|
9
|
+
ALL_COMMANDS = ( PORTFOLIO_COMMANDS + REPO_COMMANDS ).freeze
|
|
10
|
+
|
|
6
11
|
def self.start( arguments:, repo_root:, tool_root:, output:, error: )
|
|
7
12
|
ensure_global_artefacts!( tool_root: tool_root )
|
|
8
13
|
|
|
9
14
|
parsed = parse_args( arguments: arguments, output: output, error: error )
|
|
10
15
|
command = parsed.fetch( :command )
|
|
11
16
|
return Runtime::EXIT_OK if command == :help
|
|
17
|
+
return Runtime::EXIT_ERROR if command == :invalid
|
|
12
18
|
|
|
13
19
|
if command == "version"
|
|
14
20
|
output.puts "#{BADGE} #{Carson::VERSION}"
|
|
15
21
|
return Runtime::EXIT_OK
|
|
16
22
|
end
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
verbose = parsed.fetch( :verbose, false )
|
|
25
|
+
|
|
26
|
+
# Portfolio commands — no repo resolution needed.
|
|
27
|
+
# onboard/offboard carry a parsed repo_root from their <repo_path> argument;
|
|
28
|
+
# list and refresh:all use the invoking CWD.
|
|
29
|
+
if %w[list refresh:all onboard offboard].include?( command )
|
|
30
|
+
effective_root = parsed.key?( :repo_root ) ? parsed.fetch( :repo_root ) : repo_root
|
|
31
|
+
runtime = Runtime.new( repo_root: effective_root, tool_root: tool_root, output: output, error: error, verbose: verbose )
|
|
32
|
+
return dispatch( parsed: parsed, runtime: runtime )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Repo commands with an explicit repo subject — resolve it.
|
|
36
|
+
if parsed.key?( :repo_subject )
|
|
37
|
+
config = Config.load( repo_root: repo_root )
|
|
38
|
+
resolved = resolve_repo_target( name: parsed.fetch( :repo_subject ), config: config )
|
|
39
|
+
if resolved.nil?
|
|
40
|
+
error.puts "#{BADGE} Not a governed repo: #{parsed.fetch( :repo_subject )}"
|
|
41
|
+
return Runtime::EXIT_ERROR
|
|
42
|
+
end
|
|
43
|
+
runtime = Runtime.new( repo_root: resolved, tool_root: tool_root, output: output, error: error, verbose: verbose )
|
|
21
44
|
return dispatch( parsed: parsed, runtime: runtime )
|
|
22
45
|
end
|
|
23
46
|
|
|
47
|
+
# Repo commands resolved from CWD.
|
|
24
48
|
target_repo_root = parsed.fetch( :repo_root, nil )
|
|
25
49
|
target_repo_root = repo_root if target_repo_root.to_s.strip.empty?
|
|
26
50
|
unless Dir.exist?( target_repo_root )
|
|
@@ -28,8 +52,15 @@ module Carson
|
|
|
28
52
|
return Runtime::EXIT_ERROR
|
|
29
53
|
end
|
|
30
54
|
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
config = Config.load( repo_root: target_repo_root )
|
|
56
|
+
resolved = resolve_cwd_repo( repo_root: target_repo_root, config: config )
|
|
57
|
+
unless resolved
|
|
58
|
+
error.puts "#{BADGE} Not inside a governed repo. Use: carson <repo> #{command} or cd into a governed repo."
|
|
59
|
+
error.puts "#{BADGE} Run carson list to see governed repositories."
|
|
60
|
+
return Runtime::EXIT_ERROR
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
runtime = Runtime.new( repo_root: resolved, tool_root: tool_root, output: output, error: error, verbose: verbose, work_dir: target_repo_root )
|
|
33
64
|
dispatch( parsed: parsed, runtime: runtime )
|
|
34
65
|
rescue ConfigError => exception
|
|
35
66
|
error.puts "#{BADGE} Configuration problem: #{exception.message}"
|
|
@@ -45,9 +76,51 @@ module Carson
|
|
|
45
76
|
preset = parse_preset_command( arguments: arguments, output: output, parser: parser )
|
|
46
77
|
return preset.merge( verbose: verbose ) unless preset.nil?
|
|
47
78
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
79
|
+
# Pre-scan for legacy grammar before OptionParser can reject tokens.
|
|
80
|
+
legacy = detect_legacy_grammar( arguments: arguments )
|
|
81
|
+
if legacy
|
|
82
|
+
error.puts "#{BADGE} #{legacy}"
|
|
83
|
+
return { command: :invalid, verbose: verbose }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
first = arguments.first
|
|
87
|
+
|
|
88
|
+
# Portfolio command as first token.
|
|
89
|
+
if PORTFOLIO_COMMANDS.include?( first )
|
|
90
|
+
arguments.shift
|
|
91
|
+
result = parse_portfolio_command( command: first, arguments: arguments, error: error )
|
|
92
|
+
return result.merge( verbose: verbose )
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Repo command as first token — resolve from CWD.
|
|
96
|
+
if REPO_COMMANDS.include?( first )
|
|
97
|
+
arguments.shift
|
|
98
|
+
result = parse_repo_command( command: first, arguments: arguments, error: error )
|
|
99
|
+
return result.merge( verbose: verbose )
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Otherwise: first token is an explicit repo subject, second is the repo command.
|
|
103
|
+
repo_subject = arguments.shift
|
|
104
|
+
repo_command = arguments.shift
|
|
105
|
+
|
|
106
|
+
if repo_command.nil? || repo_command.strip.empty?
|
|
107
|
+
error.puts "#{BADGE} Unknown command: #{repo_subject}. Run carson --help for usage."
|
|
108
|
+
return { command: :invalid, verbose: verbose }
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Catch portfolio commands used with a repo subject.
|
|
112
|
+
if PORTFOLIO_COMMANDS.include?( repo_command ) && !REPO_COMMANDS.include?( repo_command )
|
|
113
|
+
error.puts "#{BADGE} #{repo_command} is a portfolio command. Use: carson #{repo_command}"
|
|
114
|
+
return { command: :invalid, verbose: verbose }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
unless REPO_COMMANDS.include?( repo_command )
|
|
118
|
+
error.puts "#{BADGE} Unknown command: #{repo_command}. Run carson --help for usage."
|
|
119
|
+
return { command: :invalid, verbose: verbose }
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
result = parse_repo_command( command: repo_command, arguments: arguments, error: error )
|
|
123
|
+
result.merge( verbose: verbose, repo_subject: repo_subject )
|
|
51
124
|
rescue OptionParser::ParseError => exception
|
|
52
125
|
error.puts "#{BADGE} #{exception.message}"
|
|
53
126
|
error.puts parser
|
|
@@ -56,11 +129,18 @@ module Carson
|
|
|
56
129
|
|
|
57
130
|
def self.build_parser
|
|
58
131
|
OptionParser.new do |parser|
|
|
59
|
-
parser.banner = "Usage: carson <command> [options]"
|
|
132
|
+
parser.banner = "Usage: carson <command> [options]\n carson <repo> <command> [options]"
|
|
60
133
|
parser.separator ""
|
|
61
134
|
parser.separator "Repository governance and workflow automation for coding agents."
|
|
62
135
|
parser.separator ""
|
|
63
|
-
parser.separator "
|
|
136
|
+
parser.separator "Portfolio commands:"
|
|
137
|
+
parser.separator " list List governed repositories"
|
|
138
|
+
parser.separator " onboard Register a repository for governance (requires repo path)"
|
|
139
|
+
parser.separator " offboard Remove a repository from governance (requires repo path)"
|
|
140
|
+
parser.separator " refresh Re-install hooks and configuration (all governed repos)"
|
|
141
|
+
parser.separator " version Show Carson version"
|
|
142
|
+
parser.separator ""
|
|
143
|
+
parser.separator "Repository commands (from CWD or with explicit repo):"
|
|
64
144
|
parser.separator " status Show repository delivery state"
|
|
65
145
|
parser.separator " setup Initialise Carson configuration"
|
|
66
146
|
parser.separator " audit Run pre-commit health checks"
|
|
@@ -71,14 +151,9 @@ module Carson
|
|
|
71
151
|
parser.separator " prune Remove stale local branches"
|
|
72
152
|
parser.separator " worktree Manage isolated coding worktrees"
|
|
73
153
|
parser.separator " housekeep Sync, reap worktrees, and prune branches"
|
|
74
|
-
parser.separator " repos List governed repositories"
|
|
75
|
-
parser.separator " onboard Register a repository for governance"
|
|
76
|
-
parser.separator " offboard Remove a repository from governance"
|
|
77
|
-
parser.separator " refresh Re-install hooks and configuration"
|
|
78
|
-
parser.separator " template Manage canonical template files"
|
|
79
154
|
parser.separator " review Manage PR review workflow"
|
|
80
|
-
parser.separator "
|
|
81
|
-
parser.separator "
|
|
155
|
+
parser.separator " template Manage canonical template files"
|
|
156
|
+
parser.separator " receive Triage and advance deliveries for one repo"
|
|
82
157
|
parser.separator ""
|
|
83
158
|
parser.separator "Run `carson <command> --help` for details on a specific command."
|
|
84
159
|
end
|
|
@@ -96,46 +171,83 @@ module Carson
|
|
|
96
171
|
nil
|
|
97
172
|
end
|
|
98
173
|
|
|
99
|
-
|
|
174
|
+
# Detects legacy grammar patterns and returns a migration message, or nil.
|
|
175
|
+
def self.detect_legacy_grammar( arguments: )
|
|
176
|
+
tokens = arguments.dup
|
|
177
|
+
|
|
178
|
+
# Check for --all anywhere.
|
|
179
|
+
if tokens.include?( "--all" )
|
|
180
|
+
return "--all has been removed. Use carson list --json to script batch operations."
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# Check first two tokens for legacy commands.
|
|
184
|
+
first = tokens[0].to_s
|
|
185
|
+
second = tokens[1].to_s
|
|
186
|
+
|
|
187
|
+
if first == "govern" || second == "govern"
|
|
188
|
+
return "carson govern has been replaced by carson <repo> receive"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
if first == "repos" || second == "repos"
|
|
192
|
+
return "carson repos has been replaced by carson list"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
nil
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# --- portfolio command routing ---
|
|
199
|
+
|
|
200
|
+
def self.parse_portfolio_command( command:, arguments:, error: )
|
|
100
201
|
case command
|
|
101
202
|
when "version"
|
|
102
203
|
{ command: "version" }
|
|
103
|
-
when "
|
|
104
|
-
|
|
204
|
+
when "list"
|
|
205
|
+
parse_list_command( arguments: arguments, error: error )
|
|
105
206
|
when "onboard"
|
|
106
207
|
parse_onboard_command( arguments: arguments, error: error )
|
|
107
208
|
when "offboard"
|
|
108
209
|
parse_offboard_command( arguments: arguments, error: error )
|
|
109
210
|
when "refresh"
|
|
110
211
|
parse_refresh_command( arguments: arguments, error: error )
|
|
111
|
-
|
|
112
|
-
|
|
212
|
+
else
|
|
213
|
+
error.puts "#{BADGE} Unknown portfolio command: #{command}"
|
|
214
|
+
{ command: :invalid }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# --- repo command routing ---
|
|
219
|
+
|
|
220
|
+
def self.parse_repo_command( command:, arguments:, error: )
|
|
221
|
+
case command
|
|
222
|
+
when "setup"
|
|
223
|
+
parse_setup_command( arguments: arguments, error: error )
|
|
224
|
+
when "deliver"
|
|
225
|
+
parse_deliver_command( arguments: arguments, error: error )
|
|
226
|
+
when "receive"
|
|
227
|
+
parse_receive_command( arguments: arguments, error: error )
|
|
228
|
+
when "sync"
|
|
229
|
+
parse_sync_command( arguments: arguments, error: error )
|
|
230
|
+
when "status"
|
|
231
|
+
parse_status_command( arguments: arguments, error: error )
|
|
232
|
+
when "audit"
|
|
233
|
+
parse_audit_command( arguments: arguments, error: error )
|
|
113
234
|
when "prune"
|
|
114
235
|
parse_prune_command( arguments: arguments, error: error )
|
|
115
|
-
when "worktree"
|
|
116
|
-
parse_worktree_subcommand( arguments: arguments, error: error )
|
|
117
|
-
when "repos"
|
|
118
|
-
parse_repos_command( arguments: arguments, error: error )
|
|
119
236
|
when "housekeep"
|
|
120
237
|
parse_housekeep_command( arguments: arguments, error: error )
|
|
121
|
-
when "
|
|
122
|
-
|
|
123
|
-
when "audit"
|
|
124
|
-
parse_audit_command( arguments: arguments, error: error )
|
|
238
|
+
when "worktree"
|
|
239
|
+
parse_worktree_subcommand( arguments: arguments, error: error )
|
|
125
240
|
when "abandon"
|
|
126
241
|
parse_abandon_command( arguments: arguments, error: error )
|
|
127
|
-
when "sync"
|
|
128
|
-
parse_sync_command( arguments: arguments, error: error )
|
|
129
|
-
when "status"
|
|
130
|
-
parse_status_command( arguments: arguments, error: error )
|
|
131
|
-
when "deliver"
|
|
132
|
-
parse_deliver_command( arguments: arguments, error: error )
|
|
133
242
|
when "recover"
|
|
134
243
|
parse_recover_command( arguments: arguments, error: error )
|
|
135
|
-
when "
|
|
136
|
-
|
|
244
|
+
when "review"
|
|
245
|
+
parse_review_subcommand( arguments: arguments, error: error )
|
|
246
|
+
when "template"
|
|
247
|
+
parse_template_subcommand( arguments: arguments, error: error )
|
|
137
248
|
else
|
|
138
|
-
{ command: command
|
|
249
|
+
error.puts "#{BADGE} Unknown repo command: #{command}"
|
|
250
|
+
{ command: :invalid }
|
|
139
251
|
end
|
|
140
252
|
end
|
|
141
253
|
|
|
@@ -177,26 +289,28 @@ module Carson
|
|
|
177
289
|
|
|
178
290
|
def self.parse_onboard_command( arguments:, error: )
|
|
179
291
|
onboard_parser = OptionParser.new do |parser|
|
|
180
|
-
parser.banner = "Usage: carson onboard
|
|
292
|
+
parser.banner = "Usage: carson onboard <REPO_PATH>"
|
|
181
293
|
parser.separator ""
|
|
182
294
|
parser.separator "Register a repository for Carson governance."
|
|
183
295
|
parser.separator "Detects the remote, installs hooks, applies templates, and runs initial audit."
|
|
184
|
-
parser.separator "Defaults to the current directory if no path is given."
|
|
185
296
|
parser.separator ""
|
|
186
297
|
parser.separator "Examples:"
|
|
187
|
-
parser.separator " carson onboard Onboard the current repository"
|
|
188
298
|
parser.separator " carson onboard ~/Dev/app Onboard a specific repository"
|
|
189
299
|
end
|
|
190
300
|
onboard_parser.parse!( arguments )
|
|
301
|
+
if arguments.empty?
|
|
302
|
+
error.puts "#{BADGE} Missing repo path. Use: carson onboard <repo_path>"
|
|
303
|
+
error.puts onboard_parser
|
|
304
|
+
return { command: :invalid }
|
|
305
|
+
end
|
|
191
306
|
if arguments.length > 1
|
|
192
|
-
error.puts "#{BADGE} Too many arguments for onboard. Use: carson onboard
|
|
307
|
+
error.puts "#{BADGE} Too many arguments for onboard. Use: carson onboard <repo_path>"
|
|
193
308
|
error.puts onboard_parser
|
|
194
309
|
return { command: :invalid }
|
|
195
310
|
end
|
|
196
|
-
repo_path = arguments.first
|
|
197
311
|
{
|
|
198
312
|
command: "onboard",
|
|
199
|
-
repo_root:
|
|
313
|
+
repo_root: File.expand_path( arguments.first )
|
|
200
314
|
}
|
|
201
315
|
rescue OptionParser::ParseError => exception
|
|
202
316
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -206,25 +320,28 @@ module Carson
|
|
|
206
320
|
|
|
207
321
|
def self.parse_offboard_command( arguments:, error: )
|
|
208
322
|
offboard_parser = OptionParser.new do |parser|
|
|
209
|
-
parser.banner = "Usage: carson offboard
|
|
323
|
+
parser.banner = "Usage: carson offboard <REPO_PATH>"
|
|
210
324
|
parser.separator ""
|
|
211
325
|
parser.separator "Remove a repository from Carson governance."
|
|
212
326
|
parser.separator "Unregisters the repo from Carson's portfolio and removes hooks."
|
|
213
|
-
parser.separator "Defaults to the current directory if no path is given."
|
|
214
327
|
parser.separator ""
|
|
215
328
|
parser.separator "Examples:"
|
|
216
|
-
parser.separator " carson offboard
|
|
329
|
+
parser.separator " carson offboard ~/Dev/app Offboard a specific repository"
|
|
217
330
|
end
|
|
218
331
|
offboard_parser.parse!( arguments )
|
|
332
|
+
if arguments.empty?
|
|
333
|
+
error.puts "#{BADGE} Missing repo path. Use: carson offboard <repo_path>"
|
|
334
|
+
error.puts offboard_parser
|
|
335
|
+
return { command: :invalid }
|
|
336
|
+
end
|
|
219
337
|
if arguments.length > 1
|
|
220
|
-
error.puts "#{BADGE} Too many arguments for offboard. Use: carson offboard
|
|
338
|
+
error.puts "#{BADGE} Too many arguments for offboard. Use: carson offboard <repo_path>"
|
|
221
339
|
error.puts offboard_parser
|
|
222
340
|
return { command: :invalid }
|
|
223
341
|
end
|
|
224
|
-
repo_path = arguments.first
|
|
225
342
|
{
|
|
226
343
|
command: "offboard",
|
|
227
|
-
repo_root:
|
|
344
|
+
repo_root: File.expand_path( arguments.first )
|
|
228
345
|
}
|
|
229
346
|
rescue OptionParser::ParseError => exception
|
|
230
347
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -232,71 +349,123 @@ module Carson
|
|
|
232
349
|
{ command: :invalid }
|
|
233
350
|
end
|
|
234
351
|
|
|
352
|
+
# --- list ---
|
|
353
|
+
|
|
354
|
+
def self.parse_list_command( arguments:, error: )
|
|
355
|
+
options = { json: false }
|
|
356
|
+
list_parser = OptionParser.new do |parser|
|
|
357
|
+
parser.banner = "Usage: carson list [--json]"
|
|
358
|
+
parser.separator ""
|
|
359
|
+
parser.separator "List all repositories governed by Carson."
|
|
360
|
+
parser.separator "Shows the portfolio of repos registered via carson onboard."
|
|
361
|
+
parser.separator ""
|
|
362
|
+
parser.separator "Options:"
|
|
363
|
+
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
364
|
+
parser.separator ""
|
|
365
|
+
parser.separator "Examples:"
|
|
366
|
+
parser.separator " carson list List governed repositories"
|
|
367
|
+
parser.separator " carson list --json Structured output for agent consumption"
|
|
368
|
+
end
|
|
369
|
+
list_parser.parse!( arguments )
|
|
370
|
+
unless arguments.empty?
|
|
371
|
+
error.puts "#{BADGE} Unexpected arguments for list: #{arguments.join( ' ' )}"
|
|
372
|
+
return { command: :invalid }
|
|
373
|
+
end
|
|
374
|
+
{ command: "list", json: options[ :json ] }
|
|
375
|
+
rescue OptionParser::ParseError => exception
|
|
376
|
+
error.puts "#{BADGE} #{exception.message}"
|
|
377
|
+
error.puts list_parser
|
|
378
|
+
{ command: :invalid }
|
|
379
|
+
end
|
|
380
|
+
|
|
235
381
|
# --- refresh ---
|
|
236
382
|
|
|
237
383
|
def self.parse_refresh_command( arguments:, error: )
|
|
238
|
-
options = { all: false }
|
|
239
384
|
refresh_parser = OptionParser.new do |parser|
|
|
240
|
-
parser.banner = "Usage: carson refresh
|
|
241
|
-
parser.separator ""
|
|
242
|
-
parser.separator "Re-install Carson hooks and configuration for a repository."
|
|
243
|
-
parser.separator "Defaults to the current directory. Use --all to refresh all governed repos."
|
|
385
|
+
parser.banner = "Usage: carson refresh"
|
|
244
386
|
parser.separator ""
|
|
245
|
-
parser.separator "
|
|
246
|
-
parser.on( "--all", "Refresh all governed repositories" ) { options[ :all ] = true }
|
|
387
|
+
parser.separator "Re-install Carson hooks and configuration for all governed repositories."
|
|
247
388
|
parser.separator ""
|
|
248
389
|
parser.separator "Examples:"
|
|
249
|
-
parser.separator " carson refresh
|
|
250
|
-
parser.separator " carson refresh --all Refresh all governed repos"
|
|
390
|
+
parser.separator " carson refresh Refresh all governed repos"
|
|
251
391
|
end
|
|
252
392
|
refresh_parser.parse!( arguments )
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
error.puts "#{BADGE} --all and repo_path are mutually exclusive. Use: carson refresh --all OR carson refresh [repo_path]"
|
|
393
|
+
unless arguments.empty?
|
|
394
|
+
error.puts "#{BADGE} Unexpected arguments for refresh: #{arguments.join( ' ' )}"
|
|
256
395
|
error.puts refresh_parser
|
|
257
396
|
return { command: :invalid }
|
|
258
397
|
end
|
|
398
|
+
{ command: "refresh:all" }
|
|
399
|
+
rescue OptionParser::ParseError => exception
|
|
400
|
+
error.puts "#{BADGE} #{exception.message}"
|
|
401
|
+
error.puts refresh_parser
|
|
402
|
+
{ command: :invalid }
|
|
403
|
+
end
|
|
259
404
|
|
|
260
|
-
|
|
405
|
+
# --- receive ---
|
|
261
406
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
407
|
+
def self.parse_receive_command( arguments:, error: )
|
|
408
|
+
options = {
|
|
409
|
+
dry_run: false,
|
|
410
|
+
json: false,
|
|
411
|
+
loop_seconds: nil
|
|
412
|
+
}
|
|
413
|
+
receive_parser = OptionParser.new do |parser|
|
|
414
|
+
parser.banner = "Usage: carson <repo> receive [--dry-run] [--json] [--loop SECONDS]"
|
|
415
|
+
parser.separator ""
|
|
416
|
+
parser.separator "Triage and advance deliveries for one repository."
|
|
417
|
+
parser.separator "Scans open PRs, classifies them, and takes action"
|
|
418
|
+
parser.separator "(merge, request review, or report). Runs once by default."
|
|
419
|
+
parser.separator ""
|
|
420
|
+
parser.separator "Options:"
|
|
421
|
+
parser.on( "--dry-run", "Run all checks but do not merge or dispatch" ) { options[ :dry_run ] = true }
|
|
422
|
+
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
423
|
+
parser.on( "--loop SECONDS", Integer, "Run continuously, sleeping SECONDS between cycles" ) do |seconds|
|
|
424
|
+
error.puts( "#{BADGE} --loop expects a positive integer" ) || ( return { command: :invalid } ) if seconds < 1
|
|
425
|
+
options[ :loop_seconds ] = seconds
|
|
426
|
+
end
|
|
427
|
+
parser.separator ""
|
|
428
|
+
parser.separator "Examples:"
|
|
429
|
+
parser.separator " carson nexus receive Triage deliveries for nexus"
|
|
430
|
+
parser.separator " carson nexus receive --dry-run Preview actions without applying them"
|
|
431
|
+
parser.separator " carson nexus receive --loop 300 Run continuously every 5 minutes"
|
|
432
|
+
end
|
|
433
|
+
receive_parser.parse!( arguments )
|
|
434
|
+
unless arguments.empty?
|
|
435
|
+
error.puts "#{BADGE} Unexpected arguments for receive: #{arguments.join( ' ' )}"
|
|
436
|
+
error.puts receive_parser
|
|
265
437
|
return { command: :invalid }
|
|
266
438
|
end
|
|
267
|
-
|
|
268
|
-
repo_path = arguments.first
|
|
269
439
|
{
|
|
270
|
-
command: "
|
|
271
|
-
|
|
440
|
+
command: "receive",
|
|
441
|
+
dry_run: options.fetch( :dry_run ),
|
|
442
|
+
json: options.fetch( :json ),
|
|
443
|
+
loop_seconds: options[ :loop_seconds ]
|
|
272
444
|
}
|
|
273
445
|
rescue OptionParser::ParseError => exception
|
|
274
446
|
error.puts "#{BADGE} #{exception.message}"
|
|
275
|
-
error.puts
|
|
447
|
+
error.puts receive_parser
|
|
276
448
|
{ command: :invalid }
|
|
277
449
|
end
|
|
278
450
|
|
|
279
451
|
# --- prune ---
|
|
280
452
|
|
|
281
453
|
def self.parse_prune_command( arguments:, error: )
|
|
282
|
-
options = {
|
|
454
|
+
options = { json: false }
|
|
283
455
|
prune_parser = OptionParser.new do |parser|
|
|
284
|
-
parser.banner = "Usage: carson prune [--
|
|
456
|
+
parser.banner = "Usage: carson prune [--json]"
|
|
285
457
|
parser.separator ""
|
|
286
458
|
parser.separator "Remove stale local branches."
|
|
287
459
|
parser.separator "Cleans up branches gone from the remote, orphan branches with merged PRs,"
|
|
288
460
|
parser.separator "and absorbed branches whose content is already on main."
|
|
289
461
|
parser.separator ""
|
|
290
462
|
parser.separator "Options:"
|
|
291
|
-
parser.on( "--all", "Prune all governed repositories" ) { options[ :all ] = true }
|
|
292
463
|
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
293
464
|
parser.separator ""
|
|
294
465
|
parser.separator "Examples:"
|
|
295
466
|
parser.separator " carson prune Clean up stale branches in this repo"
|
|
296
|
-
parser.separator " carson prune --all Clean up across all governed repos"
|
|
297
467
|
end
|
|
298
468
|
prune_parser.parse!( arguments )
|
|
299
|
-
return { command: "prune:all", json: options[ :json ] } if options[ :all ]
|
|
300
469
|
{ command: "prune", json: options[ :json ] }
|
|
301
470
|
rescue OptionParser::ParseError => exception
|
|
302
471
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -427,7 +596,6 @@ module Carson
|
|
|
427
596
|
end
|
|
428
597
|
|
|
429
598
|
action = arguments.shift
|
|
430
|
-
return { command: "template:check:all" } if action == "check" && arguments.include?( "--all" )
|
|
431
599
|
return { command: "template:#{action}" } unless action == "apply"
|
|
432
600
|
|
|
433
601
|
options = { push_prep: false }
|
|
@@ -458,29 +626,26 @@ module Carson
|
|
|
458
626
|
# --- audit ---
|
|
459
627
|
|
|
460
628
|
def self.parse_audit_command( arguments:, error: )
|
|
461
|
-
options = { json: false
|
|
629
|
+
options = { json: false }
|
|
462
630
|
audit_parser = OptionParser.new do |parser|
|
|
463
|
-
parser.banner = "Usage: carson audit [--
|
|
631
|
+
parser.banner = "Usage: carson audit [--json]"
|
|
464
632
|
parser.separator ""
|
|
465
633
|
parser.separator "Run pre-commit health checks on the repository."
|
|
466
634
|
parser.separator "Validates hooks, main-branch sync, PR status, and CI baseline."
|
|
467
635
|
parser.separator "Exits with a non-zero status when policy violations are found."
|
|
468
636
|
parser.separator ""
|
|
469
637
|
parser.separator "Options:"
|
|
470
|
-
parser.on( "--all", "Audit all governed repositories" ) { options[ :all ] = true }
|
|
471
638
|
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
472
639
|
parser.separator ""
|
|
473
640
|
parser.separator "Examples:"
|
|
474
641
|
parser.separator " carson audit Check repository health (also the default command)"
|
|
475
642
|
parser.separator " carson audit --json Structured output for agent consumption"
|
|
476
|
-
parser.separator " carson audit --all Audit all governed repos"
|
|
477
643
|
end
|
|
478
644
|
audit_parser.parse!( arguments )
|
|
479
645
|
unless arguments.empty?
|
|
480
646
|
error.puts "#{BADGE} Unexpected arguments for audit: #{arguments.join( ' ' )}"
|
|
481
647
|
return { command: :invalid }
|
|
482
648
|
end
|
|
483
|
-
return { command: "audit:all" } if options[ :all ]
|
|
484
649
|
{ command: "audit", json: options[ :json ] }
|
|
485
650
|
rescue OptionParser::ParseError => exception
|
|
486
651
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -523,28 +688,25 @@ module Carson
|
|
|
523
688
|
# --- sync ---
|
|
524
689
|
|
|
525
690
|
def self.parse_sync_command( arguments:, error: )
|
|
526
|
-
options = { json: false
|
|
691
|
+
options = { json: false }
|
|
527
692
|
sync_parser = OptionParser.new do |parser|
|
|
528
|
-
parser.banner = "Usage: carson sync [--
|
|
693
|
+
parser.banner = "Usage: carson sync [--json]"
|
|
529
694
|
parser.separator ""
|
|
530
695
|
parser.separator "Sync the local main branch with the remote."
|
|
531
696
|
parser.separator "Fetches and fast-forwards main without switching branches."
|
|
532
697
|
parser.separator ""
|
|
533
698
|
parser.separator "Options:"
|
|
534
|
-
parser.on( "--all", "Sync all governed repositories" ) { options[ :all ] = true }
|
|
535
699
|
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
536
700
|
parser.separator ""
|
|
537
701
|
parser.separator "Examples:"
|
|
538
702
|
parser.separator " carson sync Pull latest changes from remote main"
|
|
539
703
|
parser.separator " carson sync --json Structured output for agent consumption"
|
|
540
|
-
parser.separator " carson sync --all Sync all governed repos"
|
|
541
704
|
end
|
|
542
705
|
sync_parser.parse!( arguments )
|
|
543
706
|
unless arguments.empty?
|
|
544
707
|
error.puts "#{BADGE} Unexpected arguments for sync: #{arguments.join( ' ' )}"
|
|
545
708
|
return { command: :invalid }
|
|
546
709
|
end
|
|
547
|
-
return { command: "sync:all" } if options[ :all ]
|
|
548
710
|
{ command: "sync", json: options[ :json ] }
|
|
549
711
|
rescue OptionParser::ParseError => exception
|
|
550
712
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -555,28 +717,25 @@ module Carson
|
|
|
555
717
|
# --- status ---
|
|
556
718
|
|
|
557
719
|
def self.parse_status_command( arguments:, error: )
|
|
558
|
-
options = { json: false
|
|
720
|
+
options = { json: false }
|
|
559
721
|
status_parser = OptionParser.new do |parser|
|
|
560
|
-
parser.banner = "Usage: carson status [--
|
|
722
|
+
parser.banner = "Usage: carson status [--json]"
|
|
561
723
|
parser.separator ""
|
|
562
724
|
parser.separator "Show the current state of the repository."
|
|
563
725
|
parser.separator "Reports branch, worktrees, open PRs, stale branches, and version."
|
|
564
726
|
parser.separator ""
|
|
565
727
|
parser.separator "Options:"
|
|
566
|
-
parser.on( "--all", "Show status for all governed repositories" ) { options[ :all ] = true }
|
|
567
728
|
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
568
729
|
parser.separator ""
|
|
569
730
|
parser.separator "Examples:"
|
|
570
731
|
parser.separator " carson status Quick overview of repository state"
|
|
571
732
|
parser.separator " carson status --json Structured output for agent consumption"
|
|
572
|
-
parser.separator " carson status --all Portfolio-wide status overview"
|
|
573
733
|
end
|
|
574
734
|
status_parser.parse!( arguments )
|
|
575
735
|
unless arguments.empty?
|
|
576
736
|
error.puts "#{BADGE} Unexpected arguments for status: #{arguments.join( ' ' )}"
|
|
577
737
|
return { command: :invalid }
|
|
578
738
|
end
|
|
579
|
-
return { command: "status:all", json: options[ :json ] } if options[ :all ]
|
|
580
739
|
{ command: "status", json: options[ :json ] }
|
|
581
740
|
rescue OptionParser::ParseError => exception
|
|
582
741
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -674,83 +833,30 @@ module Carson
|
|
|
674
833
|
{ command: :invalid }
|
|
675
834
|
end
|
|
676
835
|
|
|
677
|
-
# --- repos ---
|
|
678
|
-
|
|
679
|
-
def self.parse_repos_command( arguments:, error: )
|
|
680
|
-
options = { json: false }
|
|
681
|
-
repos_parser = OptionParser.new do |parser|
|
|
682
|
-
parser.banner = "Usage: carson repos [--json]"
|
|
683
|
-
parser.separator ""
|
|
684
|
-
parser.separator "List all repositories governed by Carson."
|
|
685
|
-
parser.separator "Shows the portfolio of repos registered via carson onboard."
|
|
686
|
-
parser.separator ""
|
|
687
|
-
parser.separator "Options:"
|
|
688
|
-
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
689
|
-
parser.separator ""
|
|
690
|
-
parser.separator "Examples:"
|
|
691
|
-
parser.separator " carson repos List governed repositories"
|
|
692
|
-
parser.separator " carson repos --json Structured output for agent consumption"
|
|
693
|
-
end
|
|
694
|
-
repos_parser.parse!( arguments )
|
|
695
|
-
unless arguments.empty?
|
|
696
|
-
error.puts "#{BADGE} Unexpected arguments for repos: #{arguments.join( ' ' )}"
|
|
697
|
-
return { command: :invalid }
|
|
698
|
-
end
|
|
699
|
-
{ command: "repos", json: options[ :json ] }
|
|
700
|
-
rescue OptionParser::ParseError => exception
|
|
701
|
-
error.puts "#{BADGE} #{exception.message}"
|
|
702
|
-
error.puts repos_parser
|
|
703
|
-
{ command: :invalid }
|
|
704
|
-
end
|
|
705
|
-
|
|
706
836
|
# --- housekeep ---
|
|
707
837
|
|
|
708
838
|
def self.parse_housekeep_command( arguments:, error: )
|
|
709
|
-
options = {
|
|
839
|
+
options = { json: false, dry_run: false }
|
|
710
840
|
housekeep_parser = OptionParser.new do |parser|
|
|
711
|
-
parser.banner = "Usage: carson housekeep [
|
|
841
|
+
parser.banner = "Usage: carson housekeep [--dry-run] [--json]"
|
|
712
842
|
parser.separator ""
|
|
713
843
|
parser.separator "Run housekeeping: sync main, reap dead worktrees, and prune stale branches."
|
|
714
|
-
parser.separator "
|
|
844
|
+
parser.separator "Operates on the current repository (or explicit repo via carson <repo> housekeep)."
|
|
715
845
|
parser.separator ""
|
|
716
846
|
parser.separator "Options:"
|
|
717
|
-
parser.on( "--all", "Housekeep all governed repositories" ) { options[ :all ] = true }
|
|
718
847
|
parser.on( "--dry-run", "Show what would be reaped/deleted without making changes" ) { options[ :dry_run ] = true }
|
|
719
848
|
parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
|
|
720
|
-
parser.on( "--loop SECONDS", Integer, "Run continuously, sleeping SECONDS between cycles (requires --all)" ) do |seconds|
|
|
721
|
-
error.puts( "#{BADGE} --loop expects a positive integer" ) || ( return { command: :invalid } ) if seconds < 1
|
|
722
|
-
options[ :loop_seconds ] = seconds
|
|
723
|
-
end
|
|
724
849
|
parser.separator ""
|
|
725
850
|
parser.separator "Examples:"
|
|
726
851
|
parser.separator " carson housekeep Housekeep the current repository"
|
|
727
852
|
parser.separator " carson housekeep --dry-run Preview what housekeep would do"
|
|
728
|
-
parser.separator " carson housekeep nexus Housekeep a named governed repo"
|
|
729
|
-
parser.separator " carson housekeep --all Housekeep all governed repos"
|
|
730
|
-
parser.separator " carson housekeep --all --loop 300 Housekeep every 5 minutes"
|
|
731
853
|
end
|
|
732
854
|
housekeep_parser.parse!( arguments )
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
error.puts
|
|
736
|
-
return { command: :invalid }
|
|
737
|
-
end
|
|
738
|
-
|
|
739
|
-
if options[ :all ] && !arguments.empty?
|
|
740
|
-
error.puts "#{BADGE} --all and repo target are mutually exclusive. Use: carson housekeep --all OR carson housekeep [repo]"
|
|
741
|
-
return { command: :invalid }
|
|
742
|
-
end
|
|
743
|
-
|
|
744
|
-
return { command: "housekeep:all", json: options[ :json ], dry_run: options[ :dry_run ], loop_seconds: options[ :loop_seconds ] } if options[ :all ]
|
|
745
|
-
|
|
746
|
-
if arguments.length > 1
|
|
747
|
-
error.puts "#{BADGE} Too many arguments for housekeep. Use: carson housekeep [repo]"
|
|
855
|
+
unless arguments.empty?
|
|
856
|
+
error.puts "#{BADGE} Unexpected arguments for housekeep: #{arguments.join( ' ' )}"
|
|
857
|
+
error.puts housekeep_parser
|
|
748
858
|
return { command: :invalid }
|
|
749
859
|
end
|
|
750
|
-
|
|
751
|
-
target = arguments.shift
|
|
752
|
-
return { command: "housekeep:target", target: target, json: options[ :json ], dry_run: options[ :dry_run ] } if target
|
|
753
|
-
|
|
754
860
|
{ command: "housekeep", json: options[ :json ], dry_run: options[ :dry_run ] }
|
|
755
861
|
rescue OptionParser::ParseError => exception
|
|
756
862
|
error.puts "#{BADGE} #{exception.message}"
|
|
@@ -758,50 +864,42 @@ module Carson
|
|
|
758
864
|
{ command: :invalid }
|
|
759
865
|
end
|
|
760
866
|
|
|
761
|
-
# ---
|
|
867
|
+
# --- repo resolution (CLI layer) ---
|
|
762
868
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
parser.separator ""
|
|
784
|
-
parser.separator "Examples:"
|
|
785
|
-
parser.separator " carson govern Triage all governed repos once"
|
|
786
|
-
parser.separator " carson govern --dry-run Preview actions without applying them"
|
|
787
|
-
parser.separator " carson govern --loop 300 Run continuously every 5 minutes"
|
|
869
|
+
# Resolves an explicit repo name/path to a governed repository path.
|
|
870
|
+
# Tries exact configured path first, then basename match (case-insensitive).
|
|
871
|
+
def self.resolve_repo_target( name:, config: )
|
|
872
|
+
repos = config.govern_repos
|
|
873
|
+
expanded = File.expand_path( name )
|
|
874
|
+
return expanded if repos.include?( expanded )
|
|
875
|
+
|
|
876
|
+
downcased = File.basename( name ).downcase
|
|
877
|
+
repos.find { |repo_path| File.basename( repo_path ).downcase == downcased }
|
|
878
|
+
end
|
|
879
|
+
|
|
880
|
+
# Resolves the CWD repo_root to a governed repository path.
|
|
881
|
+
# Canonicalises worktree vs main-tree via git common-dir, then matches.
|
|
882
|
+
# Compares real paths to handle symlinks (e.g., /tmp → /private/tmp on macOS).
|
|
883
|
+
def self.resolve_cwd_repo( repo_root:, config: )
|
|
884
|
+
canonical = canonicalise_repo_root( repo_root: repo_root )
|
|
885
|
+
repos = config.govern_repos
|
|
886
|
+
repos.find do |repo_path|
|
|
887
|
+
expanded = File.expand_path( repo_path )
|
|
888
|
+
expanded == canonical || ( File.exist?( expanded ) && File.realpath( expanded ) == canonical )
|
|
788
889
|
end
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
890
|
+
end
|
|
891
|
+
|
|
892
|
+
# Returns the canonical main worktree root for a repo_root.
|
|
893
|
+
# If inside a worktree, follows git-common-dir back to the main tree.
|
|
894
|
+
def self.canonicalise_repo_root( repo_root: )
|
|
895
|
+
stdout, _, status = Open3.capture3( "git", "-C", repo_root, "rev-parse", "--path-format=absolute", "--git-common-dir" )
|
|
896
|
+
if status.success? && !stdout.strip.empty?
|
|
897
|
+
return File.dirname( stdout.strip )
|
|
794
898
|
end
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
loop_seconds: options[ :loop_seconds ]
|
|
800
|
-
}
|
|
801
|
-
rescue OptionParser::ParseError => exception
|
|
802
|
-
error.puts "#{BADGE} #{exception.message}"
|
|
803
|
-
error.puts govern_parser
|
|
804
|
-
{ command: :invalid }
|
|
899
|
+
|
|
900
|
+
File.expand_path( repo_root )
|
|
901
|
+
rescue StandardError
|
|
902
|
+
File.expand_path( repo_root )
|
|
805
903
|
end
|
|
806
904
|
|
|
807
905
|
# --- global artefacts ---
|
|
@@ -811,7 +909,7 @@ module Carson
|
|
|
811
909
|
# referenced by Claude Code's PreToolUse hook. It must exist regardless of
|
|
812
910
|
# whether `carson refresh` has been run in any governed repo.
|
|
813
911
|
def self.ensure_global_artefacts!( tool_root: )
|
|
814
|
-
source = File.join( tool_root, "hooks", "command-guard" )
|
|
912
|
+
source = File.join( tool_root, "config", ".github", "hooks", "command-guard" )
|
|
815
913
|
return unless File.file?( source )
|
|
816
914
|
|
|
817
915
|
hooks_base = File.expand_path( "~/.carson/hooks" )
|
|
@@ -844,8 +942,6 @@ module Carson
|
|
|
844
942
|
runtime.sync!( json_output: parsed.fetch( :json, false ) )
|
|
845
943
|
when "prune"
|
|
846
944
|
runtime.prune!( json_output: parsed.fetch( :json, false ) )
|
|
847
|
-
when "prune:all"
|
|
848
|
-
runtime.prune_all!
|
|
849
945
|
when "worktree:create"
|
|
850
946
|
runtime.worktree_create!( name: parsed.fetch( :worktree_name ), json_output: parsed.fetch( :json, false ) )
|
|
851
947
|
when "worktree:list"
|
|
@@ -854,8 +950,6 @@ module Carson
|
|
|
854
950
|
runtime.worktree_remove!( worktree_path: parsed.fetch( :worktree_path ), force: parsed.fetch( :force, false ), json_output: parsed.fetch( :json, false ) )
|
|
855
951
|
when "onboard"
|
|
856
952
|
runtime.onboard!
|
|
857
|
-
when "refresh"
|
|
858
|
-
runtime.refresh!
|
|
859
953
|
when "refresh:all"
|
|
860
954
|
runtime.refresh_all!
|
|
861
955
|
when "offboard"
|
|
@@ -880,37 +974,16 @@ module Carson
|
|
|
880
974
|
runtime.review_gate!
|
|
881
975
|
when "review:sweep"
|
|
882
976
|
runtime.review_sweep!
|
|
883
|
-
when "
|
|
884
|
-
runtime.
|
|
885
|
-
when "
|
|
886
|
-
runtime.
|
|
887
|
-
when "housekeep:target"
|
|
888
|
-
runtime.housekeep_target!( target: parsed.fetch( :target ), json_output: parsed.fetch( :json, false ), dry_run: parsed.fetch( :dry_run, false ) )
|
|
889
|
-
when "housekeep:all"
|
|
890
|
-
loop_seconds = parsed.fetch( :loop_seconds, nil )
|
|
891
|
-
if loop_seconds
|
|
892
|
-
runtime.housekeep_loop!(
|
|
893
|
-
json_output: parsed.fetch( :json, false ),
|
|
894
|
-
dry_run: parsed.fetch( :dry_run, false ),
|
|
895
|
-
loop_seconds: loop_seconds
|
|
896
|
-
)
|
|
897
|
-
else
|
|
898
|
-
runtime.housekeep_all!( json_output: parsed.fetch( :json, false ), dry_run: parsed.fetch( :dry_run, false ) )
|
|
899
|
-
end
|
|
900
|
-
when "govern"
|
|
901
|
-
runtime.govern!(
|
|
977
|
+
when "list"
|
|
978
|
+
runtime.list!( json_output: parsed.fetch( :json, false ) )
|
|
979
|
+
when "receive"
|
|
980
|
+
runtime.receive!(
|
|
902
981
|
dry_run: parsed.fetch( :dry_run, false ),
|
|
903
982
|
json_output: parsed.fetch( :json, false ),
|
|
904
983
|
loop_seconds: parsed.fetch( :loop_seconds, nil )
|
|
905
984
|
)
|
|
906
|
-
when "
|
|
907
|
-
runtime.
|
|
908
|
-
when "audit:all"
|
|
909
|
-
runtime.audit_all!
|
|
910
|
-
when "sync:all"
|
|
911
|
-
runtime.sync_all!
|
|
912
|
-
when "status:all"
|
|
913
|
-
runtime.status_all!( json_output: parsed.fetch( :json, false ) )
|
|
985
|
+
when "housekeep"
|
|
986
|
+
runtime.housekeep!( json_output: parsed.fetch( :json, false ), dry_run: parsed.fetch( :dry_run, false ) )
|
|
914
987
|
else
|
|
915
988
|
runtime.send( :puts_line, "Unknown command: #{command}" )
|
|
916
989
|
Runtime::EXIT_ERROR
|