carson 4.3.1 → 4.3.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.
@@ -1,60 +1,66 @@
1
- # The warehouse's vault concern.
2
- # The vault is local main where accepted parcels live.
3
- # In local-centred workstyle, the vault is the source of truth.
4
- # In remote-centred workstyle, the vault is the backup (receives
5
- # the standard from the bureau's registry after acceptance).
1
+ # The vault — where the production standard lives.
2
+ # The vault is local main. It is the source of truth.
3
+ # Accepted parcels live here permanently.
6
4
  require "open3"
7
5
 
8
6
  module Carson
9
7
  class Warehouse
10
- module Vault
8
+ class Vault
9
+ attr_reader :main_label
10
+
11
+ def initialize( path:, main_label: )
12
+ @path = path
13
+ @main_label = main_label
14
+ end
11
15
 
12
16
  # Accept a parcel into the vault.
13
- # Fast-forwards local main to include the parcel's branch.
14
- # Runs from the main worktree root where main is checked out.
17
+ # Fast-forwards the standard to include the parcel's branch.
15
18
  #
16
19
  # Precondition: the parcel's branch must be a fast-forward of main.
17
20
  # If not, the agent must rebase first.
18
- #
19
- # Returns a result hash:
20
- # { status: "ok", branch: ..., head: ... }
21
- # { status: "block", error: ..., recovery: ... }
22
- # { status: "error", error: ..., recovery: ... }
23
21
  def accept!( parcel )
24
- root = main_worktree_root
25
-
26
- # Verify main is checked out in the main worktree.
27
- unless main_checked_out_at?( root )
22
+ unless main_checked_out?
28
23
  return {
29
24
  status: "error",
30
25
  error: "#{@main_label} is not checked out in the main worktree.",
31
- recovery: "Check the main worktree state at #{root}."
26
+ recovery: "Check the main worktree state at #{@path}."
32
27
  }
33
28
  end
34
29
 
35
- # Fast-forward main to include the parcel's branch.
36
30
  _, stderr, status = Open3.capture3(
37
- "git", "-C", root, "merge", "--ff-only", parcel.label
31
+ "git", "-C", @path, "merge", "--ff-only", parcel.label
38
32
  )
39
33
 
40
- return vault_accepted( parcel, root ) if status.success?
34
+ return accepted( parcel ) if status.success?
41
35
 
42
- vault_blocked( parcel, stderr )
36
+ blocked( parcel, stderr )
37
+ end
38
+
39
+ # Has this label's content been absorbed into the vault?
40
+ # Content-aware — compares tree content, not SHA ancestry.
41
+ # Catches rebase-merged and squash-merged branches that
42
+ # ancestry-based checks miss (replayed SHAs differ).
43
+ def absorbed?( label )
44
+ _, _, status = Open3.capture3(
45
+ "git", "diff", "--quiet", @main_label, label,
46
+ chdir: @path
47
+ )
48
+ status.success?
43
49
  end
44
50
 
45
51
  private
46
52
 
47
- # Check whether main is the checked-out branch at a given path.
48
- def main_checked_out_at?( root )
53
+ # Is main checked out in the vault's worktree?
54
+ def main_checked_out?
49
55
  head_ref, _, status = Open3.capture3(
50
- "git", "-C", root, "rev-parse", "--abbrev-ref", "HEAD"
56
+ "git", "-C", @path, "rev-parse", "--abbrev-ref", "HEAD"
51
57
  )
52
58
  status.success? && head_ref.strip == @main_label
53
59
  end
54
60
 
55
- # Build the success result after vault acceptance.
56
- def vault_accepted( parcel, root )
57
- new_head, = Open3.capture3( "git", "-C", root, "rev-parse", "HEAD" )
61
+ # Build the success result after acceptance.
62
+ def accepted( parcel )
63
+ new_head, = Open3.capture3( "git", "-C", @path, "rev-parse", "HEAD" )
58
64
  {
59
65
  status: "ok",
60
66
  branch: parcel.label,
@@ -62,15 +68,24 @@ module Carson
62
68
  }
63
69
  end
64
70
 
65
- # Build the blocked/error result when vault acceptance fails.
66
- def vault_blocked( parcel, stderr )
67
- {
68
- status: "block",
69
- error: "#{parcel.label} cannot be fast-forwarded into #{@main_label}.",
70
- recovery: "Rebase onto #{@main_label} and deliver again."
71
- }
71
+ # Build the blocked result when acceptance fails.
72
+ # Distinguishes dirty-tree conflicts from diverged-history blocks
73
+ # so the agent gets the correct recovery advice.
74
+ def blocked( parcel, stderr )
75
+ if stderr.to_s.include?( "would be overwritten" )
76
+ {
77
+ status: "block",
78
+ error: "Main worktree has uncommitted changes that conflict with #{parcel.label}.",
79
+ recovery: "Commit or discard the dirty files in the main worktree, then deliver again."
80
+ }
81
+ else
82
+ {
83
+ status: "block",
84
+ error: "#{parcel.label} cannot be fast-forwarded into #{@main_label}.",
85
+ recovery: "Rebase onto #{@main_label} and deliver again."
86
+ }
87
+ end
72
88
  end
73
-
74
89
  end
75
90
  end
76
91
  end
@@ -104,8 +104,8 @@ module Carson
104
104
 
105
105
  # --- Lifecycle ---
106
106
 
107
- # Build a new workbench from the latest production standard.
108
- # Creates the directory, branches from the latest standard,
107
+ # Build a new workbench from local main.
108
+ # Creates the directory, branches from the local standard,
109
109
  # ensures .claude/ is excluded from git status.
110
110
  def build_workbench!( name: )
111
111
  root = main_worktree_root
@@ -119,23 +119,12 @@ module Carson
119
119
  recovery: "carson worktree remove #{name}, then retry" }
120
120
  end
121
121
 
122
- # Determine the base branch.
123
- base = @main_label
124
-
125
- # Fetch to update remote tracking ref without mutating the main worktree.
126
- _, _, fetch_ok = git( "fetch", @bureau_address, base )
127
- if fetch_ok.success?
128
- remote_ref = "#{@bureau_address}/#{base}"
129
- _, _, ref_ok = git( "rev-parse", "--verify", remote_ref )
130
- base = remote_ref if ref_ok.success?
131
- end
132
-
133
122
  # Ensure .claude/ is excluded from git status.
134
123
  ensure_claude_dir_excluded!
135
124
 
136
125
  # Create the worktree with a new branch.
137
126
  FileUtils.mkdir_p( File.dirname( workbench_path ) )
138
- wt_stdout, wt_stderr, wt_status = git( "worktree", "add", workbench_path, "-b", name, base )
127
+ wt_stdout, wt_stderr, wt_status = git( "worktree", "add", workbench_path, "-b", name, @main_label )
139
128
  unless wt_status.success?
140
129
  error_text = wt_stderr.to_s.strip
141
130
  error_text = "unable to create worktree" if error_text.empty?
@@ -159,11 +148,10 @@ module Carson
159
148
  path: workbench_path, branch: name }
160
149
  end
161
150
 
162
- # Agent checks in — prepare a fresh workbench from the latest standard.
151
+ # Agent checks in — prepare a fresh workbench from local main.
163
152
  # Sweeps delivered workbenches first — the Warehouse cleans behind the agent.
164
153
  def checkin!( name: )
165
- receive_latest_standard!
166
- sweep_delivered_workbenches!
154
+ sweep!
167
155
  result = build_workbench!( name: name )
168
156
  result[ :command ] = "checkin"
169
157
  result
@@ -237,19 +225,20 @@ module Carson
237
225
  end
238
226
 
239
227
  # Full safety assessment before removal.
228
+ # Asks the workbench about its own state — no duplicate checks.
240
229
  # Returns { status: :ok } or { status: :block/:error, error:, recovery: }.
241
230
  def assess_removal( workbench, force: false, skip_unpushed: false )
242
231
  unless workbench.exists?
243
232
  return { status: :ok, missing: true }
244
233
  end
245
234
 
246
- if agent_at_workbench?( workbench )
235
+ if workbench.holds_cwd?
247
236
  return { status: :block, result_status: "block",
248
237
  error: "current working directory is inside this worktree",
249
- recovery: "cd #{main_worktree_root} && carson worktree remove #{File.basename( workbench.path )}" }
238
+ recovery: "cd #{main_worktree_root} && carson checkout #{File.basename( workbench.path )}" }
250
239
  end
251
240
 
252
- if workbench_held_by_process?( workbench )
241
+ if workbench.held_by_other_process?
253
242
  return { status: :block, result_status: "block",
254
243
  error: "another process has its working directory inside this worktree",
255
244
  recovery: "wait for the other session to finish, then retry" }
@@ -269,9 +258,12 @@ module Carson
269
258
  { status: :ok, missing: false }
270
259
  end
271
260
 
272
- # Sweep stale workbenches. Walk all agent-owned workbenches,
273
- # check state, tear down those safe to reap. Repair missing ones.
274
- def sweep_workbenches!
261
+ # The warehouse sweeps autonomous housekeeping.
262
+ # Walks all agent-owned workbenches. Asks each one about its state.
263
+ # If the label has been absorbed into the vault, and the workbench
264
+ # isn't sealed or occupied — safe to remove, tears it down.
265
+ # Repairs missing ones.
266
+ def sweep!
275
267
  root = main_worktree_root
276
268
 
277
269
  agent_prefixes = AGENT_DIRS.map do |dir|
@@ -284,48 +276,17 @@ module Carson
284
276
  next unless workbench.branch
285
277
  next unless agent_prefixes.any? { |prefix| workbench.path.start_with?( prefix ) }
286
278
 
287
- # Use the existing classifier if available (transitional).
288
- if respond_to?( :classify_worktree_cleanup, true )
289
- classification = classify_worktree_cleanup( worktree: workbench )
290
- next unless classification.fetch( :action ) == :reap
291
- end
292
-
293
279
  unless workbench.exists?
294
280
  repair_missing_workbench!( workbench )
295
281
  next
296
282
  end
297
283
 
298
- _, _, rm_ok = git( "worktree", "remove", workbench.path )
299
- next unless rm_ok.success?
300
-
301
- if workbench.branch
302
- git( "branch", "-D", workbench.branch )
303
- end
304
- end
305
- end
306
-
307
- private
308
-
309
- # --- Sweep ---
310
-
311
- # Sweep delivered workbenches — branches absorbed into main, not sealed,
312
- # not CWD-blocked. Called by checkin! so the Warehouse cleans behind the agent.
313
- def sweep_delivered_workbenches!
314
- root = main_worktree_root
315
-
316
- agent_prefixes = AGENT_DIRS.map do |dir|
317
- full = File.join( root, dir, "worktrees" )
318
- File.join( realpath_safe( full ), "" ) if Dir.exist?( full )
319
- end.compact
320
- return if agent_prefixes.empty?
284
+ # Only sweep workbenches whose content has been absorbed into the vault.
285
+ next unless absorbed?( workbench.branch )
321
286
 
322
- workbenches.each do |workbench|
323
- next unless workbench.branch
324
- next unless agent_prefixes.any? { |prefix| workbench.path.start_with?( prefix ) }
325
- next unless workbench.exists?
326
- next unless label_absorbed?( workbench.branch )
327
- next if agent_at_workbench?( workbench )
328
- next if workbench_held_by_process?( workbench )
287
+ # Ask the workbench about its own state — not occupied, not held.
288
+ next if workbench.holds_cwd?
289
+ next if workbench.held_by_other_process?
329
290
 
330
291
  # Do not sweep sealed workbenches — parcel still in flight.
331
292
  seal_check = Warehouse.new( path: workbench.path )
@@ -338,41 +299,7 @@ module Carson
338
299
  end
339
300
  end
340
301
 
341
- # --- Safety checks ---
342
-
343
- # Is the agent's working directory inside this workbench?
344
- def agent_at_workbench?( workbench )
345
- cwd = realpath_safe( Dir.pwd )
346
- workbench_path = realpath_safe( workbench.path )
347
- normalised = File.join( workbench_path, "" )
348
- cwd == workbench_path || cwd.start_with?( normalised )
349
- rescue StandardError
350
- false
351
- end
352
-
353
- # Is another process occupying this workbench?
354
- def workbench_held_by_process?( workbench )
355
- canonical = realpath_safe( workbench.path )
356
- return false if canonical.nil? || canonical.empty?
357
- return false unless Dir.exist?( canonical )
358
-
359
- stdout, = Open3.capture3( "lsof", "-d", "cwd" )
360
- return false if stdout.nil? || stdout.empty?
361
-
362
- normalised = File.join( canonical, "" )
363
- my_pid = Process.pid
364
- stdout.lines.drop( 1 ).any? do |line|
365
- fields = line.strip.split( /\s+/ )
366
- next false unless fields.length >= 9
367
- next false if fields[ 1 ].to_i == my_pid
368
- name = fields[ 8.. ].join( " " )
369
- name == canonical || name.start_with?( normalised )
370
- end
371
- rescue Errno::ENOENT
372
- false
373
- rescue StandardError
374
- false
375
- end
302
+ private
376
303
 
377
304
  # Would tearing down lose unpushed work?
378
305
  # Content-aware: compares tree content vs main, not SHAs.
@@ -1,7 +1,10 @@
1
- # A governed repository. In the FedEx metaphor, the warehouse is where
2
- # parcels are built on workbenches (worktrees) with labels (branches).
3
- # Git and gh commands are hidden inside callers never see git or
4
- # GitHub terms.
1
+ # A governed repository the intelligent, self-managing building
2
+ # where parcels are built on workbenches. Each warehouse belongs
3
+ # to a client. The warehouse is the local authority everything
4
+ # inside the repository is its domain.
5
+ #
6
+ # At the heart of the warehouse is the vault — where the production
7
+ # standard lives. The vault is the source of truth.
5
8
  require "fileutils"
6
9
  require "open3"
7
10
 
@@ -11,13 +14,8 @@ require_relative "warehouse/seal"
11
14
  require_relative "warehouse/bureau"
12
15
 
13
16
  module Carson
14
- # A governed repository — the warehouse where parcels are built on
15
- # workbenches (worktrees) with labels (branches). An intelligent
16
- # warehouse that manages itself: packing parcels, checking compliance,
17
- # managing workbenches, and sweeping up.
18
17
  class Warehouse
19
18
  include Workbench
20
- include Vault
21
19
  include Seal
22
20
  include Bureau
23
21
 
@@ -30,29 +28,46 @@ module Carson
30
28
  @compliance_checker = compliance_checker
31
29
  end
32
30
 
31
+ # --- The vault ---
32
+
33
+ # The vault — where the production standard lives.
34
+ def vault
35
+ @vault ||= Vault.new( path: main_worktree_root, main_label: @main_label )
36
+ end
37
+
38
+ # Accept a parcel into the vault.
39
+ def accept!( parcel )
40
+ vault.accept!( parcel )
41
+ end
42
+
43
+ # Has this label been absorbed into the vault?
44
+ def absorbed?( label )
45
+ vault.absorbed?( label )
46
+ end
47
+
33
48
  # --- What the warehouse knows ---
34
49
 
35
- # The label on the current workbench (branch name).
50
+ # The label on the current workbench.
36
51
  def current_label
37
52
  git( "rev-parse", "--abbrev-ref", "HEAD" ).first.strip
38
53
  end
39
54
 
40
- # The tip of the parcel on the current workbench (commit SHA).
55
+ # The tip of the parcel on the current workbench.
41
56
  def current_head
42
57
  git( "rev-parse", "HEAD" ).first.strip
43
58
  end
44
59
 
45
- # The destination label (from config).
60
+ # What the production standard is called.
46
61
  def main_label
47
62
  @main_label
48
63
  end
49
64
 
50
- # The bureau's address (remote name).
65
+ # The bureau's address where to send things.
51
66
  def bureau_address
52
67
  @bureau_address
53
68
  end
54
69
 
55
- # Is the warehouse floor clean? No uncommitted changes on the current workbench.
70
+ # Is the floor clean? No loose material lying around.
56
71
  def clean?
57
72
  output, _, status = git( "status", "--porcelain" )
58
73
  status.success? && output.strip.empty?
@@ -60,47 +75,36 @@ module Carson
60
75
 
61
76
  # --- Warehouse operations ---
62
77
 
63
- # Ship a parcel to the bureau.
64
- # The warehouse sends the parcel's label to the remote.
78
+ # Ship a parcel to the backup so the courier can work with it.
65
79
  def ship( parcel, remote: bureau_address )
66
80
  _, _, status = git( "push", "-u", remote, parcel.label )
67
81
  status.success?
68
82
  end
69
83
 
70
- # Get latest registry state from the bureau (git fetch).
71
- # Returns true on success, false on failure.
72
- def fetch_latest( remote: bureau_address, registry: nil )
73
- arguments = [ "fetch", remote ]
74
- arguments << registry if registry
75
- _, _, status = git( *arguments )
84
+ # Is this parcel based on the latest standard?
85
+ # The standard is vault state — is the parcel built on top of it?
86
+ def based_on_latest?( parcel )
87
+ standard = "#{bureau_address}/#{main_label}"
88
+ _, _, status = git( "merge-base", "--is-ancestor", standard, parcel.head )
76
89
  status.success?
77
90
  end
78
91
 
79
- # Is the parcel based on the client's latest production standard?
80
- # Checks whether the registry tip is an ancestor of the parcel's head.
81
- def based_on_latest_standard?( parcel, registry: "#{bureau_address}/#{main_label}" )
82
- _, _, status = git( "merge-base", "--is-ancestor", registry, parcel.head )
83
- status.success?
84
- end
85
-
86
- # Ensure the warehouse complies with company standards (template sync).
87
- # Delegates to the injected compliance checker. If no checker is set,
88
- # the warehouse assumes compliance — no templates to enforce.
92
+ # Submit compliance ensure the warehouse meets company standards.
89
93
  def submit_compliance!
90
94
  return { compliant: true, committed: false } unless @compliance_checker
91
95
 
92
96
  @compliance_checker.call( self )
93
97
  end
94
98
 
95
- # Update the warehouse's production standard — rebase onto latest registry state.
96
- # Called after the bureau refuses a parcel for being behind standard.
97
- def rebase_on_latest_standard!( registry: "#{bureau_address}/#{main_label}" )
98
- _, _, status = git( "rebase", registry )
99
+ # Rebase a workbench onto the latest standard.
100
+ # When a parcel falls behind the standard, the warehouse fixes it.
101
+ def rebase!( standard: "#{bureau_address}/#{main_label}" )
102
+ _, _, status = git( "rebase", standard )
99
103
  status.success?
100
104
  end
101
105
 
102
- # Pack a parcel — stage all changes and commit.
103
- # Refuses if the workbench is sealed (parcel already in flight).
106
+ # Pack a parcel — stage all loose material and seal it.
107
+ # Refuses if the workbench is sealed — a parcel is already in flight.
104
108
  def pack!( message: )
105
109
  if sealed?
106
110
  raise "Branch is locked — PR ##{sealed_tracking_number} in flight. " \
@@ -111,14 +115,14 @@ module Carson
111
115
  status.success?
112
116
  end
113
117
 
114
- # Receive the latest standard from the registry after a parcel is accepted.
115
- # Fast-forwards local main without switching branches.
118
+ # Receive the latest standard.
119
+ # After a parcel is accepted, the standard has changed. The warehouse
120
+ # updates its vault without disturbing the current workbench.
116
121
  #
117
- # Two paths depending on the main worktree's checkout state:
122
+ # Two paths depending on the vault's checkout state:
118
123
  # - Main checked out → merge --ff-only (updates ref + working tree).
119
- # - Main not checked out → fetch refspec (updates ref only, safe when
120
- # no worktree has the branch).
121
- def receive_latest_standard!( remote: bureau_address )
124
+ # - Main not checked out → fetch refspec (updates ref only).
125
+ def receive_latest!( remote: bureau_address )
122
126
  root = main_worktree_root
123
127
 
124
128
  _, _, fetch_status = Open3.capture3( "git", "-C", root, "fetch", remote )
@@ -145,73 +149,60 @@ module Carson
145
149
  # --- Delivery prep ---
146
150
 
147
151
  # Prepare a parcel for delivery.
148
- # Orchestrates the prep phase: pack, fetch, standard check, auto-rebase.
149
- # Returns { status: "ok" } or { status: "block"/"error", error:, recovery: }.
152
+ # Packs if the agent provided a message, checks if the parcel is based
153
+ # on the latest standard, rebases automatically if it's behind.
150
154
  def prepare!( parcel, message: nil )
151
- registry = "#{bureau_address}/#{main_label}"
155
+ standard = "#{bureau_address}/#{main_label}"
152
156
 
153
- # Pack if the agent provided a commit message.
154
157
  if message
155
158
  unless pack!( message: message )
156
159
  return { status: "error", error: "Nothing to commit.", recovery: "Stage changes first." }
157
160
  end
158
- # Update the parcel's head after packing.
159
161
  parcel = Parcel.new( label: parcel.label, head: current_head )
160
162
  end
161
163
 
162
- # Fetch the latest standard.
163
- unless fetch_latest
164
+ unless receive_latest!
164
165
  return {
165
166
  status: "block",
166
- error: "Cannot fetch latest standard.",
167
+ error: "Cannot receive latest standard.",
167
168
  recovery: "Check network and remote config, then deliver again."
168
169
  }
169
170
  end
170
171
 
171
- # Check if the parcel is based on the latest standard.
172
- unless based_on_latest_standard?( parcel, registry: registry )
173
- # Auto-rebase onto the latest standard.
174
- unless rebase_on_latest_standard!( registry: registry )
172
+ unless based_on_latest?( parcel )
173
+ unless rebase!( standard: standard )
175
174
  return {
176
175
  status: "block",
177
176
  error: "#{parcel.label} conflicts with #{@main_label}.",
178
177
  recovery: "Rebase onto #{@main_label}, resolve conflicts, deliver again."
179
178
  }
180
179
  end
181
- # Update the parcel's head after rebase.
182
180
  parcel = Parcel.new( label: parcel.label, head: current_head )
183
181
  end
184
182
 
183
+ # Stamp the parcel with its origin so it knows whether it carries anything.
184
+ origin, = git( "merge-base", main_label, parcel.label )
185
+ parcel = Parcel.new( label: parcel.label, head: parcel.head, shelf: parcel.shelf, origin: origin.strip )
186
+
187
+ if parcel.empty?
188
+ return {
189
+ status: "block",
190
+ error: "Nothing to deliver — no commits ahead of #{main_label}.",
191
+ recovery: "Commit changes, then carson deliver."
192
+ }
193
+ end
194
+
185
195
  { status: "ok", parcel: parcel }
186
196
  end
187
197
 
188
198
  # --- Inventory ---
189
199
 
190
- # All labels (branch names).
200
+ # All labels in the warehouse.
191
201
  def labels
192
202
  output, = git( "branch", "--format", "%(refname:short)" )
193
203
  output.lines.map { it.strip }.reject { it.empty? }
194
204
  end
195
205
 
196
- # Has this label's content been absorbed into main?
197
- # Content-aware: compares tree content, not SHA ancestry.
198
- # Catches rebase-merged and squash-merged branches that
199
- # `git branch --merged` misses (replayed SHAs differ).
200
- def label_absorbed?( name )
201
- _, _, status = Open3.capture3(
202
- "git", "diff", "--quiet", @main_label, name,
203
- chdir: path )
204
- status.success?
205
- end
206
-
207
- # All worktree paths (transitional — use workbenches for Worktree instances).
208
- def shelves
209
- output, = git( "worktree", "list", "--porcelain" )
210
- output.lines
211
- .select { it.start_with?( "worktree " ) }
212
- .map { it.sub( "worktree ", "" ).strip }
213
- end
214
-
215
206
  # The main warehouse location — resolves correctly even from a workbench.
216
207
  def main_worktree_root
217
208
  git_common_dir, = git( "rev-parse", "--path-format=absolute", "--git-common-dir" )
@@ -279,7 +279,7 @@ module Carson
279
279
  resolved_path: resolved_path,
280
280
  branch: branch,
281
281
  error: "current working directory is inside this worktree",
282
- recovery: "cd #{safe_root} && carson worktree remove #{File.basename( resolved_path )}"
282
+ recovery: "cd #{safe_root} && carson checkout #{File.basename( resolved_path )}"
283
283
  }
284
284
  end
285
285
 
data/lib/cli.rb CHANGED
@@ -5,7 +5,7 @@ require "optparse"
5
5
  module Carson
6
6
  class CLI
7
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 checkin checkout].freeze
8
+ REPO_COMMANDS = %w[deliver receive sync status prune housekeep worktree abandon recover review template setup checkin checkout].freeze
9
9
  ALL_COMMANDS = ( PORTFOLIO_COMMANDS + REPO_COMMANDS ).freeze
10
10
 
11
11
  def self.start( arguments:, repo_root:, tool_root:, output:, error: )
@@ -154,7 +154,7 @@ module Carson
154
154
  return { command: :help }
155
155
  end
156
156
  return { command: "version" } if [ "--version", "-v" ].include?( first )
157
- return { command: "audit" } if arguments.empty?
157
+ return { command: "status" } if arguments.empty?
158
158
 
159
159
  nil
160
160
  end
@@ -217,8 +217,6 @@ module Carson
217
217
  parse_sync_command( arguments: arguments, error: error )
218
218
  when "status"
219
219
  parse_status_command( arguments: arguments, error: error )
220
- when "audit"
221
- parse_audit_command( arguments: arguments, error: error )
222
220
  when "prune"
223
221
  parse_prune_command( arguments: arguments, error: error )
224
222
  when "housekeep"
@@ -284,7 +282,7 @@ module Carson
284
282
  parser.banner = "Usage: carson onboard <REPO_PATH>"
285
283
  parser.separator ""
286
284
  parser.separator "Register a repository for Carson governance."
287
- parser.separator "Detects the remote, installs hooks, applies templates, and runs initial audit."
285
+ parser.separator "Detects the remote, installs hooks, and applies templates."
288
286
  parser.separator ""
289
287
  parser.separator "Examples:"
290
288
  parser.separator " carson onboard ~/Dev/app Onboard a specific repository"
@@ -677,36 +675,6 @@ module Carson
677
675
  { command: :invalid }
678
676
  end
679
677
 
680
- # --- audit ---
681
-
682
- def self.parse_audit_command( arguments:, error: )
683
- options = { json: false }
684
- audit_parser = OptionParser.new do |parser|
685
- parser.banner = "Usage: carson audit [--json]"
686
- parser.separator ""
687
- parser.separator "Run pre-commit health checks on the repository."
688
- parser.separator "Validates hooks, main-branch sync, PR status, and CI baseline."
689
- parser.separator "Exits with a non-zero status when policy violations are found."
690
- parser.separator ""
691
- parser.separator "Options:"
692
- parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
693
- parser.separator ""
694
- parser.separator "Examples:"
695
- parser.separator " carson audit Check repository health (also the default command)"
696
- parser.separator " carson audit --json Structured output for agent consumption"
697
- end
698
- audit_parser.parse!( arguments )
699
- unless arguments.empty?
700
- error.puts "#{BADGE} Unexpected arguments for audit: #{arguments.join( ' ' )}"
701
- return { command: :invalid }
702
- end
703
- { command: "audit", json: options[ :json ] }
704
- rescue OptionParser::ParseError => exception
705
- error.puts "#{BADGE} #{exception.message}"
706
- error.puts audit_parser
707
- { command: :invalid }
708
- end
709
-
710
678
  # --- abandon ---
711
679
 
712
680
  def self.parse_abandon_command( arguments:, error: )
@@ -854,7 +822,7 @@ module Carson
854
822
  parser.banner = "Usage: carson recover --check NAME [--json]"
855
823
  parser.separator ""
856
824
  parser.separator "Merge the current repair PR when one governance-owned required check is already red on the default branch."
857
- parser.separator "Recovery is narrow: Carson verifies the baseline failure, keeps every other gate intact, and records an audit event."
825
+ parser.separator "Recovery is narrow: Carson verifies the baseline failure, keeps every other gate intact, and records a recovery event."
858
826
  parser.separator ""
859
827
  parser.separator "Options:"
860
828
  parser.on( "--check NAME", "Name of the governance-owned required check to recover" ) { |value| options[ :check_name ] = value }
@@ -988,8 +956,6 @@ module Carson
988
956
  runtime.status!( json_output: parsed.fetch( :json, false ) )
989
957
  when "setup"
990
958
  runtime.setup!( cli_choices: parsed.fetch( :cli_choices, {} ) )
991
- when "audit"
992
- runtime.audit!( json_output: parsed.fetch( :json, false ) )
993
959
  when "abandon"
994
960
  runtime.abandon!( target: parsed.fetch( :target ), json_output: parsed.fetch( :json, false ) )
995
961
  when "sync"