carson 4.1.1 → 4.2.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.
@@ -1,18 +1,24 @@
1
1
  # A governed repository. In the FedEx metaphor, the warehouse is where
2
- # parcels (committed changes) are stored on shelves (worktrees) with
3
- # labels (branches). Git and gh commands are hidden inside — callers
4
- # never see git or GitHub terms.
5
- require "digest"
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.
6
5
  require "fileutils"
7
- require "json"
8
6
  require "open3"
9
7
 
8
+ require_relative "warehouse/workbench"
9
+ require_relative "warehouse/seal"
10
+ require_relative "warehouse/bureau"
11
+
10
12
  module Carson
11
- # A governed repository — the warehouse where parcels are stored on
12
- # shelves (worktrees) with labels (branches). Wraps git operations
13
- # with story-language methods. An intelligent warehouse that manages
14
- # itself: packing parcels, checking compliance, and sweeping up.
13
+ # A governed repository — the warehouse where parcels are built on
14
+ # workbenches (worktrees) with labels (branches). An intelligent
15
+ # warehouse that manages itself: packing parcels, checking compliance,
16
+ # managing workbenches, and sweeping up.
15
17
  class Warehouse
18
+ include Workbench
19
+ include Seal
20
+ include Bureau
21
+
16
22
  attr_reader :path
17
23
 
18
24
  def initialize( path:, main_label: "main", bureau_address: "github", compliance_checker: nil )
@@ -24,12 +30,12 @@ module Carson
24
30
 
25
31
  # --- What the warehouse knows ---
26
32
 
27
- # The label on the current shelf (branch name).
33
+ # The label on the current workbench (branch name).
28
34
  def current_label
29
35
  git( "rev-parse", "--abbrev-ref", "HEAD" ).first.strip
30
36
  end
31
37
 
32
- # The tip of the parcel on the current shelf (commit SHA).
38
+ # The tip of the parcel on the current workbench (commit SHA).
33
39
  def current_head
34
40
  git( "rev-parse", "HEAD" ).first.strip
35
41
  end
@@ -44,7 +50,7 @@ module Carson
44
50
  @bureau_address
45
51
  end
46
52
 
47
- # Is the warehouse floor clean? No uncommitted changes on the current shelf.
53
+ # Is the warehouse floor clean? No uncommitted changes on the current workbench.
48
54
  def clean?
49
55
  output, _, status = git( "status", "--porcelain" )
50
56
  status.success? && output.strip.empty?
@@ -78,7 +84,6 @@ module Carson
78
84
  # Ensure the warehouse complies with company standards (template sync).
79
85
  # Delegates to the injected compliance checker. If no checker is set,
80
86
  # the warehouse assumes compliance — no templates to enforce.
81
- # Returns a hash: { compliant: true/false, committed: true/false, error: nil/string }
82
87
  def submit_compliance!
83
88
  return { compliant: true, committed: false } unless @compliance_checker
84
89
 
@@ -87,15 +92,13 @@ module Carson
87
92
 
88
93
  # Update the warehouse's production standard — rebase onto latest registry state.
89
94
  # Called after the bureau refuses a parcel for being behind standard.
90
- # Returns true on success, false on failure.
91
95
  def rebase_on_latest_standard!( registry: "#{bureau_address}/#{main_label}" )
92
96
  _, _, status = git( "rebase", registry )
93
97
  status.success?
94
98
  end
95
99
 
96
100
  # Pack a parcel — stage all changes and commit.
97
- # Refuses if the shelf is sealed (parcel already in flight).
98
- # Returns true on success, false on failure.
101
+ # Refuses if the workbench is sealed (parcel already in flight).
99
102
  def pack!( message: )
100
103
  if sealed?
101
104
  raise "Branch is locked — PR ##{sealed_tracking_number} in flight. " \
@@ -106,38 +109,8 @@ module Carson
106
109
  status.success?
107
110
  end
108
111
 
109
- # --- Shelf seal ---
110
-
111
- # Seal the shelf — no more packing until the delivery outcome is confirmed.
112
- # The courier seals the shelf after shipping and filing the waybill.
113
- # The marker lives outside the worktree (~/.carson/seals/) so it does
114
- # not pollute git status or block delivery with a dirty-tree guard.
115
- def seal_shelf!( tracking_number: )
116
- marker = delivering_marker_path
117
- FileUtils.mkdir_p( File.dirname( marker ) )
118
- File.write( marker, "#{tracking_number}\n#{@path}" )
119
- end
120
-
121
- # Unseal the shelf — the courier brought back the parcel.
122
- # Called when the delivery outcome is held or rejected.
123
- def unseal_shelf!
124
- File.delete( delivering_marker_path ) if File.exist?( delivering_marker_path )
125
- end
126
-
127
- # Is this shelf sealed for a delivery in flight?
128
- def sealed?
129
- File.exist?( delivering_marker_path )
130
- end
131
-
132
- # The tracking number of the in-flight delivery (nil if not sealed).
133
- def sealed_tracking_number
134
- return nil unless sealed?
135
- File.read( delivering_marker_path ).lines.first.strip
136
- end
137
-
138
112
  # Receive the latest standard from the registry after a parcel is accepted.
139
113
  # Fast-forwards local main without switching branches.
140
- # Returns true on success, false on failure.
141
114
  #
142
115
  # Two paths depending on the main worktree's checkout state:
143
116
  # - Main checked out → merge --ff-only (updates ref + working tree).
@@ -146,24 +119,20 @@ module Carson
146
119
  def receive_latest_standard!( remote: bureau_address )
147
120
  root = main_worktree_root
148
121
 
149
- # Fetch remote tracking refs — always safe, even when main is checked out.
150
122
  _, _, fetch_status = Open3.capture3( "git", "-C", root, "fetch", remote )
151
123
  return false unless fetch_status.success?
152
124
 
153
- # Determine how to advance local main.
154
125
  head_ref, _, head_status = Open3.capture3(
155
126
  "git", "-C", root, "rev-parse", "--abbrev-ref", "HEAD"
156
127
  )
157
128
  return false unless head_status.success?
158
129
 
159
130
  if head_ref.strip == @main_label
160
- # Main is checked out in the main worktree — fast-forward via merge.
161
131
  _, _, merge_status = Open3.capture3(
162
132
  "git", "-C", root, "merge", "--ff-only", "#{remote}/#{@main_label}"
163
133
  )
164
134
  merge_status.success?
165
135
  else
166
- # Main is not checked out — safe to update the ref via fetch refspec.
167
136
  _, _, refspec_status = Open3.capture3(
168
137
  "git", "-C", root, "fetch", remote, "#{@main_label}:#{@main_label}"
169
138
  )
@@ -171,72 +140,8 @@ module Carson
171
140
  end
172
141
  end
173
142
 
174
- # --- Bureau interaction ---
175
- # The warehouse owns the connection to the bureau (GitHub).
176
- # It queries, files, and registers on behalf of the courier.
177
-
178
- # Check the parcel's status at the bureau using the waybill.
179
- # Calls gh pr view + gh pr checks. Records findings onto the waybill.
180
- def check_parcel_at_bureau_with( waybill )
181
- state = fetch_pr_state_for( waybill.tracking_number )
182
- ci, ci_diagnostic = fetch_ci_state_for( waybill.tracking_number )
183
- waybill.record( state: state, ci: ci, ci_diagnostic: ci_diagnostic )
184
- end
185
-
186
- # File a waybill at the bureau for this parcel.
187
- # Calls gh pr create. Returns a Waybill with tracking number, or nil on failure.
188
- def file_waybill_for!( parcel, title: nil, body_file: nil )
189
- filing_title = title || Waybill.default_title_for( parcel.label )
190
- arguments = [ "pr", "create", "--title", filing_title, "--head", parcel.label ]
191
-
192
- if body_file && File.exist?( body_file )
193
- arguments.push( "--body-file", body_file )
194
- else
195
- arguments.push( "--body", "" )
196
- end
197
-
198
- stdout, _, status = gh( *arguments )
199
- tracking_number = nil
200
- url = nil
201
-
202
- if status.success?
203
- url = stdout.to_s.strip
204
- tracking_number = url.split( "/" ).last.to_i
205
- tracking_number = nil if tracking_number == 0
206
- end
207
-
208
- # If create failed or returned no number, try to find an existing PR.
209
- unless tracking_number
210
- tracking_number, url = find_existing_waybill_for( parcel.label )
211
- end
212
-
213
- return nil unless tracking_number
214
-
215
- Waybill.new( label: parcel.label, tracking_number: tracking_number, url: url )
216
- end
217
-
218
- # Register the parcel at the bureau using the waybill.
219
- # Calls gh pr merge. Stamps the waybill on success.
220
- def register_parcel_at_bureau_with!( waybill, method: )
221
- _, _, status = gh( "pr", "merge", waybill.tracking_number.to_s, "--#{method}" )
222
- if status.success?
223
- waybill.stamp( :accepted )
224
- else
225
- # Re-check the state — the merge may have revealed a new blocker.
226
- check_parcel_at_bureau_with( waybill )
227
- end
228
- end
229
-
230
143
  # --- Inventory ---
231
144
 
232
- # All shelves (worktree paths).
233
- def shelves
234
- output, = git( "worktree", "list", "--porcelain" )
235
- output.lines
236
- .select { it.start_with?( "worktree " ) }
237
- .map { it.sub( "worktree ", "" ).strip }
238
- end
239
-
240
145
  # All labels (branch names).
241
146
  def labels
242
147
  output, = git( "branch", "--format", "%(refname:short)" )
@@ -249,88 +154,31 @@ module Carson
249
154
  merged_output.lines.map { it.strip }.include?( name )
250
155
  end
251
156
 
252
- # The main warehouse locationresolves correctly even from a side shelf.
253
- # Used by sync! and ledger recording to always reference the canonical path.
157
+ # All worktree paths (transitionaluse workbenches for Worktree instances).
158
+ def shelves
159
+ output, = git( "worktree", "list", "--porcelain" )
160
+ output.lines
161
+ .select { it.start_with?( "worktree " ) }
162
+ .map { it.sub( "worktree ", "" ).strip }
163
+ end
164
+
165
+ # The main warehouse location — resolves correctly even from a workbench.
254
166
  def main_worktree_root
255
167
  git_common_dir, = git( "rev-parse", "--path-format=absolute", "--git-common-dir" )
256
168
  common = git_common_dir.strip
257
- # If it ends with /.git, the parent is the main worktree root.
258
169
  common.end_with?( "/.git" ) ? File.dirname( common ) : common
259
170
  end
260
171
 
261
172
  private
262
173
 
263
- # Path to the delivery marker file — signals the shelf is sealed.
264
- # Lives outside the worktree at ~/.carson/seals/<sha256-of-path>
265
- # so it does not pollute git status.
266
- def delivering_marker_path
267
- seals_dir = File.join( Dir.home, ".carson", "seals" )
268
- key = Digest::SHA256.hexdigest( @path )
269
- File.join( seals_dir, key )
270
- end
271
-
272
174
  # All git commands go through this single gateway.
273
- # Returns [stdout, stderr, status].
274
175
  def git( *arguments )
275
176
  Open3.capture3( "git", "-C", path, *arguments )
276
177
  end
277
178
 
278
179
  # All gh commands go through this single gateway.
279
- # Returns [stdout, stderr, status].
280
180
  def gh( *arguments )
281
181
  Open3.capture3( "gh", *arguments, chdir: path )
282
182
  end
283
-
284
- # Fetch PR state from the bureau for a tracking number.
285
- # Returns the parsed state hash, or nil on failure.
286
- def fetch_pr_state_for( tracking_number )
287
- stdout, _, status = gh(
288
- "pr", "view", tracking_number.to_s,
289
- "--json", "number,state,isDraft,url,mergeStateStatus,mergeable,mergedAt"
290
- )
291
- return nil unless status.success?
292
-
293
- JSON.parse( stdout )
294
- rescue JSON::ParserError
295
- nil
296
- end
297
-
298
- # Fetch CI state from the bureau for a tracking number.
299
- # Returns [ci_symbol, diagnostic_or_nil].
300
- # Captures the first line of stderr as diagnostic when the command fails.
301
- def fetch_ci_state_for( tracking_number )
302
- stdout, stderr, status = gh(
303
- "pr", "checks", tracking_number.to_s,
304
- "--json", "name,bucket"
305
- )
306
- unless status.success?
307
- return [ :error, stderr.to_s.strip.lines.first&.strip ]
308
- end
309
-
310
- checks = JSON.parse( stdout ) rescue []
311
- return [ :none, nil ] if checks.empty?
312
-
313
- buckets = checks.map { it[ "bucket" ].to_s.downcase }
314
- return [ :fail, nil ] if buckets.include?( "fail" )
315
- return [ :pending, nil ] if buckets.include?( "pending" )
316
-
317
- [ :pass, nil ]
318
- end
319
-
320
- # Try to find an existing PR for this label at the bureau.
321
- # Returns [tracking_number, url] or [nil, nil].
322
- def find_existing_waybill_for( label )
323
- stdout, _, status = gh(
324
- "pr", "view", label,
325
- "--json", "number,url,state"
326
- )
327
- if status.success?
328
- data = JSON.parse( stdout ) rescue nil
329
- if data && data[ "number" ] && data[ "state" ] == "OPEN"
330
- return [ data[ "number" ], data[ "url" ].to_s ]
331
- end
332
- end
333
- [ nil, nil ]
334
- end
335
183
  end
336
184
  end
@@ -420,6 +420,16 @@ module Carson
420
420
  false
421
421
  end
422
422
 
423
+ # Is the workbench surface clean? (no uncommitted changes)
424
+ def clean?
425
+ return false unless exists?
426
+
427
+ stdout, = Open3.capture3( "git", "status", "--porcelain", chdir: path )
428
+ stdout.to_s.strip.empty?
429
+ rescue StandardError
430
+ false
431
+ end
432
+
423
433
  # rubocop:disable Layout/AccessModifierIndentation -- tab-width calculation produces unfixable mixed tabs+spaces
424
434
  private
425
435
  # rubocop:enable Layout/AccessModifierIndentation
data/lib/carson.rb CHANGED
@@ -93,4 +93,4 @@ require_relative "carson/adapters/prompt"
93
93
  require_relative "carson/adapters/codex"
94
94
  require_relative "carson/adapters/claude"
95
95
  require_relative "carson/runtime"
96
- require_relative "carson/cli"
96
+ require_relative "cli"
@@ -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].freeze
8
+ REPO_COMMANDS = %w[deliver receive sync status audit 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: )
@@ -229,6 +229,10 @@ module Carson
229
229
  parse_housekeep_command( arguments: arguments, error: error )
230
230
  when "worktree"
231
231
  parse_worktree_subcommand( arguments: arguments, error: error )
232
+ when "checkin"
233
+ parse_checkin_command( arguments: arguments, error: error )
234
+ when "checkout"
235
+ parse_checkout_command( arguments: arguments, error: error )
232
236
  when "abandon"
233
237
  parse_abandon_command( arguments: arguments, error: error )
234
238
  when "recover"
@@ -523,6 +527,71 @@ module Carson
523
527
  { command: :invalid }
524
528
  end
525
529
 
530
+ # --- checkin ---
531
+
532
+ def self.parse_checkin_command( arguments:, error: )
533
+ options = { json: false }
534
+ checkin_parser = OptionParser.new do |parser|
535
+ parser.banner = "Usage: carson checkin <name> [--json]"
536
+ parser.separator ""
537
+ parser.separator "Prepare a fresh workbench from the latest standard."
538
+ parser.separator "The agent names the workbench — it becomes the branch."
539
+ parser.separator ""
540
+ parser.separator "Options:"
541
+ parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
542
+ parser.separator ""
543
+ parser.separator "Examples:"
544
+ parser.separator " carson checkin feature-auth"
545
+ parser.separator " carson checkin oo/refactor-courier"
546
+ end
547
+ checkin_parser.parse!( arguments )
548
+ name = arguments.shift.to_s.strip
549
+ if name.empty?
550
+ error.puts "#{BADGE} Missing name. Use: carson checkin <name>"
551
+ error.puts checkin_parser
552
+ return { command: :invalid }
553
+ end
554
+
555
+ { command: "checkin", workbench_name: name, json: options.fetch( :json ) }
556
+ rescue OptionParser::ParseError => exception
557
+ error.puts "#{BADGE} #{exception.message}"
558
+ error.puts checkin_parser
559
+ { command: :invalid }
560
+ end
561
+
562
+ # --- checkout ---
563
+
564
+ def self.parse_checkout_command( arguments:, error: )
565
+ options = { json: false, force: false }
566
+ checkout_parser = OptionParser.new do |parser|
567
+ parser.banner = "Usage: carson checkout <name> [--json] [--force]"
568
+ parser.separator ""
569
+ parser.separator "Release a workbench when safe."
570
+ parser.separator "Removes the directory and local branch."
571
+ parser.separator ""
572
+ parser.separator "Options:"
573
+ parser.on( "--json", "Machine-readable JSON output" ) { options[ :json ] = true }
574
+ parser.on( "--force", "Skip safety checks" ) { options[ :force ] = true }
575
+ parser.separator ""
576
+ parser.separator "Examples:"
577
+ parser.separator " carson checkout feature-auth"
578
+ parser.separator " carson checkout oo/refactor-courier --force"
579
+ end
580
+ checkout_parser.parse!( arguments )
581
+ name = arguments.shift.to_s.strip
582
+ if name.empty?
583
+ error.puts "#{BADGE} Missing name. Use: carson checkout <name>"
584
+ error.puts checkout_parser
585
+ return { command: :invalid }
586
+ end
587
+
588
+ { command: "checkout", workbench_name: name, force: options.fetch( :force ), json: options.fetch( :json ) }
589
+ rescue OptionParser::ParseError => exception
590
+ error.puts "#{BADGE} #{exception.message}"
591
+ error.puts checkout_parser
592
+ { command: :invalid }
593
+ end
594
+
526
595
  # --- review ---
527
596
 
528
597
  def self.parse_review_subcommand( arguments:, error: )
@@ -898,7 +967,7 @@ module Carson
898
967
  # referenced by Claude Code's PreToolUse hook. It must exist regardless of
899
968
  # whether `carson refresh` has been run in any governed repo.
900
969
  def self.ensure_global_artefacts!( tool_root: )
901
- source = File.join( tool_root, "config", ".github", "hooks", "command-guard" )
970
+ source = File.join( tool_root, "config", "hooks", "command-guard" )
902
971
  return unless File.file?( source )
903
972
 
904
973
  hooks_base = File.expand_path( "~/.carson/hooks" )
@@ -937,6 +1006,10 @@ module Carson
937
1006
  runtime.worktree_list!( json_output: parsed.fetch( :json, false ) )
938
1007
  when "worktree:remove"
939
1008
  runtime.worktree_remove!( worktree_path: parsed.fetch( :worktree_path ), force: parsed.fetch( :force, false ), json_output: parsed.fetch( :json, false ) )
1009
+ when "checkin"
1010
+ dispatch_checkin( parsed: parsed, runtime: runtime )
1011
+ when "checkout"
1012
+ dispatch_checkout( parsed: parsed, runtime: runtime )
940
1013
  when "onboard"
941
1014
  runtime.onboard!
942
1015
  when "refresh:all"
@@ -978,5 +1051,77 @@ module Carson
978
1051
  Runtime::EXIT_ERROR
979
1052
  end
980
1053
  end
1054
+
1055
+ # --- Direct Warehouse dispatch for checkin/checkout ---
1056
+ # No Runtime — CLI builds the Warehouse and calls domain methods directly.
1057
+ # This is the target architecture; other commands migrate here as Runtime dissolves.
1058
+
1059
+ def self.dispatch_checkin( parsed:, runtime: )
1060
+ warehouse = build_warehouse( runtime: runtime )
1061
+ result = warehouse.checkin!( name: parsed.fetch( :workbench_name ) )
1062
+ report_workbench( result: result, json: parsed.fetch( :json, false ), output: runtime.output )
1063
+ end
1064
+
1065
+ def self.dispatch_checkout( parsed:, runtime: )
1066
+ warehouse = build_warehouse( runtime: runtime )
1067
+ workbench = warehouse.workbench_named( parsed.fetch( :workbench_name ) )
1068
+
1069
+ unless workbench
1070
+ name = parsed.fetch( :workbench_name )
1071
+ result = { command: "checkout", status: "error",
1072
+ name: name,
1073
+ error: "#{name} is not a registered workbench",
1074
+ recovery: "carson worktree list" }
1075
+ return report_workbench( result: result, json: parsed.fetch( :json, false ), output: runtime.output )
1076
+ end
1077
+
1078
+ result = warehouse.checkout!( workbench, force: parsed.fetch( :force, false ) )
1079
+ report_workbench( result: result, json: parsed.fetch( :json, false ), output: runtime.output )
1080
+ end
1081
+
1082
+ # Build a Warehouse rooted at the main worktree.
1083
+ # Uses runtime's resolved root and config for remote/branch names.
1084
+ def self.build_warehouse( runtime: )
1085
+ Warehouse.new(
1086
+ path: runtime.send( :main_worktree_root ),
1087
+ main_label: runtime.config.main_branch,
1088
+ bureau_address: runtime.config.git_remote
1089
+ )
1090
+ end
1091
+
1092
+ # Render a workbench result as JSON or human text.
1093
+ # Returns the appropriate exit code.
1094
+ def self.report_workbench( result:, json:, output: )
1095
+ status = result[ :status ]
1096
+ exit_code = case status
1097
+ when "ok" then Runtime::EXIT_OK
1098
+ when "block" then Runtime::EXIT_BLOCK
1099
+ else Runtime::EXIT_ERROR
1100
+ end
1101
+
1102
+ if json
1103
+ output.puts JSON.pretty_generate( result )
1104
+ else
1105
+ case status
1106
+ when "ok"
1107
+ case result[ :command ]
1108
+ when "checkin"
1109
+ output.puts "#{BADGE} Workbench ready: #{result[ :name ]}"
1110
+ output.puts " path: #{result[ :path ]}"
1111
+ output.puts " branch: #{result[ :branch ]}"
1112
+ when "checkout"
1113
+ output.puts "#{BADGE} Workbench released: #{result[ :name ]}"
1114
+ end
1115
+ when "error"
1116
+ output.puts "#{BADGE} #{result[ :error ]}"
1117
+ output.puts " \u2192 #{result[ :recovery ]}" if result[ :recovery ]
1118
+ when "block"
1119
+ output.puts "#{BADGE} #{result[ :error ]}"
1120
+ output.puts " \u2192 #{result[ :recovery ]}" if result[ :recovery ]
1121
+ end
1122
+ end
1123
+
1124
+ exit_code
1125
+ end
981
1126
  end
982
1127
  end
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.1.1
4
+ version: 4.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang
@@ -10,27 +10,7 @@ authors:
10
10
  bindir: exe
11
11
  cert_chain: []
12
12
  date: 1980-01-02 00:00:00.000000000 Z
13
- dependencies:
14
- - !ruby/object:Gem::Dependency
15
- name: sqlite3
16
- requirement: !ruby/object:Gem::Requirement
17
- requirements:
18
- - - ">="
19
- - !ruby/object:Gem::Version
20
- version: '1.3'
21
- - - "<"
22
- - !ruby/object:Gem::Version
23
- version: '3'
24
- type: :runtime
25
- prerelease: false
26
- version_requirements: !ruby/object:Gem::Requirement
27
- requirements:
28
- - - ">="
29
- - !ruby/object:Gem::Version
30
- version: '1.3'
31
- - - "<"
32
- - !ruby/object:Gem::Version
33
- version: '3'
13
+ dependencies: []
34
14
  description: 'Carson is an autonomous git strategist and repositories governor that
35
15
  lives outside the repositories it governs — no Carson-owned artefacts in your repo.
36
16
  As strategist, Carson knows when to branch, how to isolate concurrent work, and
@@ -53,11 +33,11 @@ files:
53
33
  - RELEASE.md
54
34
  - VERSION
55
35
  - carson.gemspec
56
- - config/.github/hooks/command-guard
57
- - config/.github/hooks/pre-commit
58
- - config/.github/hooks/pre-merge-commit
59
- - config/.github/hooks/pre-push
60
- - config/.github/hooks/prepare-commit-msg
36
+ - config/hooks/command-guard
37
+ - config/hooks/pre-commit
38
+ - config/hooks/pre-merge-commit
39
+ - config/hooks/pre-push
40
+ - config/hooks/prepare-commit-msg
61
41
  - exe/carson
62
42
  - icon.svg
63
43
  - lib/carson.rb
@@ -68,7 +48,6 @@ files:
68
48
  - lib/carson/adapters/github.rb
69
49
  - lib/carson/adapters/prompt.rb
70
50
  - lib/carson/branch.rb
71
- - lib/carson/cli.rb
72
51
  - lib/carson/config.rb
73
52
  - lib/carson/courier.rb
74
53
  - lib/carson/delivery.rb
@@ -103,8 +82,12 @@ files:
103
82
  - lib/carson/runtime/status.rb
104
83
  - lib/carson/version.rb
105
84
  - lib/carson/warehouse.rb
85
+ - lib/carson/warehouse/bureau.rb
86
+ - lib/carson/warehouse/seal.rb
87
+ - lib/carson/warehouse/workbench.rb
106
88
  - lib/carson/waybill.rb
107
89
  - lib/carson/worktree.rb
90
+ - lib/cli.rb
108
91
  homepage: https://github.com/wanghailei/carson
109
92
  licenses:
110
93
  - PolyForm-Shield-1.0.0
File without changes
File without changes
File without changes
File without changes
File without changes