carson 4.1.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c882d441c7ea190db45ec65d5aad771de7a601c19375204dfabaa6ec285a6ebf
4
- data.tar.gz: 6b7cc35fa68c90a5d493409f8bbc7799dd1cc2adacb2af0ff15cf42393d66c9c
3
+ metadata.gz: 57e828abcdfc77caa38be92fa08a41d8d13abd37a09e7a4bf726d07253a48749
4
+ data.tar.gz: 5a6141be7046db72d03971fc9c4f45f1b7c81a47d685b13c97810711ebe9db39
5
5
  SHA512:
6
- metadata.gz: c28c1b696376b7949f505481c22908dabc8c4c813ae71b9d461e11d1298143e89fa4490a0f6a00a7a4730880aa0b5b3d9916559dfa3181d595bf181c3e62eca8
7
- data.tar.gz: c202d344de6b324aae1e9dc9d8ad31a7378c6404c248daca3084d919caa5e21068cc64a6bf38d0e50deccd41e88ee5c0680dac51702410469f6906b5f35903f9
6
+ metadata.gz: 409ab608b1bed1423f16aa257efcc53f608643112db23020ac1786bd77fcc94a1776df32991be7ff0c00eb5d9b31f6db454e1ea4ad6020b65aaeb4dd186e39aa
7
+ data.tar.gz: a482b66aa5220c17d9d1b4c2852cc10b9cfeaa0d8af803e0b7974c20994754b1eb2affbb4d3a5659804764a398f25f7c822ffe3feaaa61b4c6b7fea46cf4b88a
data/API.md CHANGED
@@ -173,7 +173,7 @@ Environment overrides:
173
173
  - `agent.codex` / `agent.claude`: provider-specific options (reserved).
174
174
  - `check_wait`: seconds to wait for CI checks before classifying (default: `30`).
175
175
  - `merge.method`: `"squash"` only in governed mode.
176
- - `state_path`: JSON ledger path for active deliveries and revisions. Legacy SQLite ledgers are imported automatically on first read; explicit legacy `.sqlite3` paths keep working after import.
176
+ - `state_path`: JSON ledger path for active deliveries and revisions.
177
177
 
178
178
  `template` schema:
179
179
 
data/RELEASE.md CHANGED
@@ -7,6 +7,25 @@ Release-note scope rule:
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 4.2.0
11
+
12
+ ### New
13
+
14
+ - **`carson checkin <name>`** — the agent's verb for getting a fresh workbench. Receives the latest production standard before building. Sweeps delivered workbenches automatically — the Warehouse cleans behind the agent, so explicit checkout is rarely needed.
15
+ - **`carson checkout <name>`** — the agent's verb for releasing a workbench when done. Checks the seal (parcel in flight blocks checkout), CWD, process holds, dirty state, and unpushed work. Use at end of session; the common case is handled by the next checkin.
16
+
17
+ ### Changed
18
+
19
+ - **CLI moved outside the story world** — `lib/carson/cli.rb` → `lib/cli.rb`. `lib/carson/` is domain objects only. checkin and checkout wire CLI directly to Warehouse with no Runtime. This is the template for Runtime dissolution.
20
+ - **`tear_down_workbench!` renamed to `remove_workbench!`** — simpler verb, matches the domain.
21
+ - **Remote branch deletion removed from workbench removal** — GitHub handles remote branch cleanup on PR merge. The Warehouse now only removes the directory and local branch.
22
+
23
+ ### UX
24
+
25
+ - `carson checkin` output: `⧓ Workbench ready: <name>` with path and branch.
26
+ - `carson checkout` output: `⧓ Workbench released: <name>`.
27
+ - Agent lifecycle simplified: `checkin → work → deliver → checkin` (repeat). No explicit checkout between tasks.
28
+
10
29
  ## 4.1.2
11
30
 
12
31
  ### Fixed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.2
1
+ 4.2.0
data/carson.gemspec CHANGED
@@ -28,7 +28,6 @@ Gem::Specification.new do |spec|
28
28
  spec.bindir = "exe"
29
29
  spec.executables = [ "carson" ]
30
30
  spec.require_paths = [ "lib" ]
31
- spec.add_dependency "sqlite3", ">= 1.3", "< 3"
32
31
  spec.files = Dir.glob( "{lib,exe,templates,config}/**/*", File::FNM_DOTMATCH ).select { |path| File.file?( path ) } + [
33
32
  ".github/workflows/carson_policy.yml",
34
33
  "README.md",
data/lib/carson/ledger.rb CHANGED
@@ -7,12 +7,10 @@ module Carson
7
7
  class Ledger
8
8
  UNSET = Object.new
9
9
  ACTIVE_DELIVERY_STATES = Delivery::ACTIVE_STATES
10
- SQLITE_HEADER = "SQLite format 3\0".b.freeze
11
10
 
12
11
  def initialize( path: )
13
12
  @path = File.expand_path( path )
14
13
  FileUtils.mkdir_p( File.dirname( @path ) )
15
- migrate_legacy_state_if_needed!
16
14
  end
17
15
 
18
16
  attr_reader :path
@@ -273,22 +271,6 @@ module Carson
273
271
  )
274
272
  end
275
273
 
276
- def migrate_legacy_state_if_needed!
277
- # Skip lock acquisition entirely when no legacy SQLite file exists.
278
- # Read-only file checks are safe without the lock; the migration
279
- # itself is idempotent so a narrow race is harmless.
280
- return unless state_path_requires_migration?
281
-
282
- with_state_lock do |lock_file|
283
- lock_file.flock( File::LOCK_EX )
284
- source_path = legacy_sqlite_source_path
285
- next unless source_path
286
-
287
- state = load_legacy_sqlite_state( path: source_path )
288
- save_state!( state )
289
- end
290
- end
291
-
292
274
  def with_state_lock
293
275
  lock_path = "#{path}.lock"
294
276
  FileUtils.mkdir_p( File.dirname( lock_path ) )
@@ -299,110 +281,6 @@ module Carson
299
281
  end
300
282
  end
301
283
 
302
- def legacy_sqlite_source_path
303
- return nil unless state_path_requires_migration?
304
- return path if sqlite_database_file?( path: path )
305
-
306
- legacy_path = legacy_state_path
307
- return nil unless legacy_path
308
- return legacy_path if sqlite_database_file?( path: legacy_path )
309
-
310
- nil
311
- end
312
-
313
- def state_path_requires_migration?
314
- return true if sqlite_database_file?( path: path )
315
- return false if File.exist?( path )
316
- !legacy_state_path.nil?
317
- end
318
-
319
- def legacy_state_path
320
- return nil unless path.end_with?( ".json" )
321
- path.sub( /\.json\z/, ".sqlite3" )
322
- end
323
-
324
- def sqlite_database_file?( path: )
325
- return false unless File.file?( path )
326
- File.binread( path, SQLITE_HEADER.bytesize ) == SQLITE_HEADER
327
- rescue StandardError
328
- false
329
- end
330
-
331
- def load_legacy_sqlite_state( path: )
332
- begin
333
- require "sqlite3"
334
- rescue LoadError => exception
335
- raise "legacy SQLite ledger found at #{path}, but sqlite3 support is unavailable: #{exception.message}"
336
- end
337
-
338
- database = open_legacy_sqlite_database( path: path )
339
- deliveries = database.execute( "SELECT * FROM deliveries ORDER BY id ASC" )
340
- revisions_by_delivery = database.execute(
341
- "SELECT * FROM revisions ORDER BY delivery_id ASC, number ASC, id ASC"
342
- ).group_by { |row| row.fetch( "delivery_id" ) }
343
-
344
- state = {
345
- "deliveries" => {},
346
- "recovery_events" => [],
347
- "next_sequence" => 1
348
- }
349
- deliveries.each do |row|
350
- key = delivery_key(
351
- repo_path: row.fetch( "repo_path" ),
352
- branch_name: row.fetch( "branch_name" ),
353
- head: row.fetch( "head" )
354
- )
355
- state[ "deliveries" ][ key ] = {
356
- "sequence" => row.fetch( "id" ).to_i,
357
- "repo_path" => row.fetch( "repo_path" ),
358
- "branch_name" => row.fetch( "branch_name" ),
359
- "head" => row.fetch( "head" ),
360
- "worktree_path" => row.fetch( "worktree_path" ),
361
- "status" => row.fetch( "status" ),
362
- "pr_number" => row.fetch( "pr_number" ),
363
- "pr_url" => row.fetch( "pr_url" ),
364
- "pull_request_state" => nil,
365
- "pull_request_draft" => nil,
366
- "pull_request_merged_at" => nil,
367
- "merge_proof" => nil,
368
- "cause" => row.fetch( "cause" ),
369
- "summary" => row.fetch( "summary" ),
370
- "created_at" => row.fetch( "created_at" ),
371
- "updated_at" => row.fetch( "updated_at" ),
372
- "integrated_at" => row.fetch( "integrated_at" ),
373
- "superseded_at" => row.fetch( "superseded_at" ),
374
- "revisions" => Array( revisions_by_delivery[ row.fetch( "id" ) ] ).map do |revision|
375
- {
376
- "number" => revision.fetch( "number" ).to_i,
377
- "cause" => revision.fetch( "cause" ),
378
- "provider" => revision.fetch( "provider" ),
379
- "status" => revision.fetch( "status" ),
380
- "started_at" => revision.fetch( "started_at" ),
381
- "finished_at" => revision.fetch( "finished_at" ),
382
- "summary" => revision.fetch( "summary" )
383
- }
384
- end
385
- }
386
- end
387
- normalise_state!( state: state )
388
- state
389
- ensure
390
- database&.close
391
- end
392
-
393
- def open_legacy_sqlite_database( path: )
394
- database = SQLite3::Database.new( "file:#{path}?immutable=1", readonly: true, uri: true )
395
- database.results_as_hash = true
396
- database.busy_timeout = 5_000
397
- database
398
- rescue SQLite3::CantOpenException
399
- database&.close
400
- database = SQLite3::Database.new( path, readonly: true )
401
- database.results_as_hash = true
402
- database.busy_timeout = 5_000
403
- database
404
- end
405
-
406
284
  def normalise_state!( state: )
407
285
  deliveries = state[ "deliveries" ]
408
286
  raise "state file must contain a JSON object at #{path}" unless deliveries.is_a?( Hash )
@@ -140,7 +140,7 @@ module Carson
140
140
  end
141
141
 
142
142
  if worktree
143
- check = worktree_warehouse.assess_teardown( worktree, force: false, skip_unpushed: true )
143
+ check = worktree_warehouse.assess_removal( worktree, force: false, skip_unpushed: true )
144
144
  return nil if check.fetch( :status ) == :ok
145
145
 
146
146
  recovery = check[ :recovery ]
@@ -26,7 +26,7 @@ module Carson
26
26
  recovery: "git worktree list" },
27
27
  json_output: json_output )
28
28
  end
29
- result = wh.tear_down_workbench!( workbench, force: force, skip_unpushed: skip_unpushed )
29
+ result = wh.remove_workbench!( workbench, force: force, skip_unpushed: skip_unpushed )
30
30
  finish_worktree( result: result, json_output: json_output )
31
31
  end
32
32
 
@@ -1,5 +1,5 @@
1
1
  # The warehouse's workbench concern.
2
- # Builds, tears down, sweeps, and inventories workbenches.
2
+ # Builds, removes, sweeps, and inventories workbenches.
3
3
  # Workbenches are passive objects — the warehouse acts on them.
4
4
  require "fileutils"
5
5
  require "open3"
@@ -159,16 +159,46 @@ module Carson
159
159
  path: workbench_path, branch: name }
160
160
  end
161
161
 
162
- # Tear down a workbench directory, registration, label.
162
+ # Agent checks in — prepare a fresh workbench from the latest standard.
163
+ # Sweeps delivered workbenches first — the Warehouse cleans behind the agent.
164
+ def checkin!( name: )
165
+ receive_latest_standard!
166
+ sweep_delivered_workbenches!
167
+ result = build_workbench!( name: name )
168
+ result[ :command ] = "checkin"
169
+ result
170
+ end
171
+
172
+ # Agent checks out — release the workbench when safe.
173
+ # A sealed workbench has a parcel in flight at the Bureau.
174
+ def checkout!( workbench, force: false )
175
+ unless force
176
+ seal_check = Warehouse.new( path: workbench.path )
177
+ if seal_check.sealed?
178
+ tracking = seal_check.sealed_tracking_number || "unknown"
179
+ return { command: "checkout", status: "block",
180
+ name: File.basename( workbench.path ), branch: workbench.branch,
181
+ error: "workbench is sealed — PR ##{tracking} is still in flight",
182
+ recovery: "wait for CI checks to complete, or run carson deliver to check status" }
183
+ end
184
+ end
185
+
186
+ result = remove_workbench!( workbench, force: force )
187
+ result[ :command ] = "checkout"
188
+ result
189
+ end
190
+
191
+ # Remove a workbench — directory, registration, local branch.
163
192
  # The warehouse checks safety before acting.
164
- def tear_down_workbench!( workbench, force: false, skip_unpushed: false )
193
+ # Remote branch cleanup is GitHub's concern, not the warehouse's.
194
+ def remove_workbench!( workbench, force: false, skip_unpushed: false )
165
195
  # If the directory is already gone, repair the stale registration.
166
196
  unless workbench.exists?
167
197
  return repair_missing_workbench!( workbench )
168
198
  end
169
199
 
170
200
  # Safety assessment.
171
- assessment = assess_teardown( workbench, force: force, skip_unpushed: skip_unpushed )
201
+ assessment = assess_removal( workbench, force: force, skip_unpushed: skip_unpushed )
172
202
  unless assessment[ :status ] == :ok
173
203
  return { command: "worktree remove", status: assessment[ :result_status ] || "error",
174
204
  name: File.basename( workbench.path ), branch: workbench.branch,
@@ -201,22 +231,14 @@ module Carson
201
231
  branch_deleted = del_ok.success?
202
232
  end
203
233
 
204
- # Step 3: delete the remote branch (best-effort).
205
- remote_deleted = false
206
- if branch
207
- _, _, rd_ok = git( "push", @bureau_address, "--delete", branch )
208
- remote_deleted = rd_ok.success?
209
- end
210
-
211
234
  { command: "worktree remove", status: "ok",
212
235
  name: File.basename( workbench.path ),
213
- branch: branch, branch_deleted: branch_deleted,
214
- remote_deleted: remote_deleted }
236
+ branch: branch, branch_deleted: branch_deleted }
215
237
  end
216
238
 
217
- # Full safety assessment before tear-down.
239
+ # Full safety assessment before removal.
218
240
  # Returns { status: :ok } or { status: :block/:error, error:, recovery: }.
219
- def assess_teardown( workbench, force: false, skip_unpushed: false )
241
+ def assess_removal( workbench, force: false, skip_unpushed: false )
220
242
  unless workbench.exists?
221
243
  return { status: :ok, missing: true }
222
244
  end
@@ -284,6 +306,38 @@ module Carson
284
306
 
285
307
  private
286
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?
321
+
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 )
329
+
330
+ # Do not sweep sealed workbenches — parcel still in flight.
331
+ seal_check = Warehouse.new( path: workbench.path )
332
+ next if seal_check.sealed?
333
+
334
+ _, _, rm_ok = git( "worktree", "remove", workbench.path )
335
+ next unless rm_ok.success?
336
+
337
+ git( "branch", "-D", workbench.branch ) if workbench.branch
338
+ end
339
+ end
340
+
287
341
  # --- Safety checks ---
288
342
 
289
343
  # Is the agent's working directory inside this workbench?
@@ -370,16 +424,9 @@ module Carson
370
424
  branch_deleted = del_ok.success?
371
425
  end
372
426
 
373
- remote_deleted = false
374
- if branch
375
- _, _, rd_ok = git( "push", @bureau_address, "--delete", branch )
376
- remote_deleted = rd_ok.success?
377
- end
378
-
379
427
  { command: "worktree remove", status: "ok",
380
428
  name: File.basename( workbench.path ),
381
- branch: branch, branch_deleted: branch_deleted,
382
- remote_deleted: remote_deleted }
429
+ branch: branch, branch_deleted: branch_deleted }
383
430
  end
384
431
 
385
432
  # --- Build helpers ---
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: )
@@ -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.2
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
@@ -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
@@ -108,6 +87,7 @@ files:
108
87
  - lib/carson/warehouse/workbench.rb
109
88
  - lib/carson/waybill.rb
110
89
  - lib/carson/worktree.rb
90
+ - lib/cli.rb
111
91
  homepage: https://github.com/wanghailei/carson
112
92
  licenses:
113
93
  - PolyForm-Shield-1.0.0