carson 3.4.0 → 3.5.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: 514509a6422a4c2237a48feac1bff1c10e4705afe62fb574d400b8364cf131ba
4
- data.tar.gz: 1f7bbf53a01ee92d8a97d62d0a7957bb823c0aabd70e31a7e27e37de8ab3b8c6
3
+ metadata.gz: e8fc2b54698568aaec7d7f4ec39ecd32f56f44a395555a34099b24ec52a1d608
4
+ data.tar.gz: e3b0a5a14c733d6b2e9f5c08475d89a53f0a3d1faf8a2fb85ed72bcb76f662ef
5
5
  SHA512:
6
- metadata.gz: a536b8a85ba6b2a737ce913485e3d58b5672b6b13af40261b181dd234bf1052ffa93a5f9b51307d9ce7e433e588b92ce294e88118b758747d4bb5cbad898c6c5
7
- data.tar.gz: d476ba9ed57c7c97967c0cb2fab8b8390debfa196d657ae6d2e6c51e143248f8160bc543eb0d9b4e5c3c434ba4a25aa16b8af9aa6ec3b9cfcf5b7c974cd8fa9e
6
+ metadata.gz: 9d3733f98a23dffdc69c1ebde71e94595c22e662f5ead86b97988fa3f5cf0e8daf56806deb82ceb1d034e0913640d9a8b7cafc26992f94e43dd96a3e304d6812
7
+ data.tar.gz: eb44eca84a943a461668b478e2bed295b3a73ee7e916cc5d85a837c52f5be09326b369e07a99812d4ffdc883914511d18c7f0489e151f3d4d66afe8ddaf97402
data/RELEASE.md CHANGED
@@ -5,6 +5,22 @@ Release-note scope rule:
5
5
  - `RELEASE.md` records only version deltas, breaking changes, and migration actions.
6
6
  - Operational usage guides live in `MANUAL.md` and `API.md`.
7
7
 
8
+ ## 3.5.0 — Sync JSON + Recovery
9
+
10
+ ### What changed
11
+
12
+ - **`carson sync --json`** — machine-readable JSON output for the sync command. The JSON envelope includes `command`, `status`, `ahead`, `behind`, `main_branch`, `remote`, `exit_code`, and on failure: `error` and `recovery`.
13
+ - **Recovery-aware sync errors** — dirty working tree now includes a recovery command in both human (`Recovery: ...`) and JSON output.
14
+
15
+ ### UX
16
+
17
+ - JSON output suppresses git command output (fetch, pull) to keep the JSON envelope clean.
18
+ - Human output remains unchanged when `--json` is not passed.
19
+
20
+ ### Migration
21
+
22
+ - No breaking changes. `carson sync` without `--json` behaves identically to 3.4.0.
23
+
8
24
  ## 3.4.0 — Audit JSON
9
25
 
10
26
  ### What changed
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.4.0
1
+ 3.5.0
data/lib/carson/cli.rb CHANGED
@@ -53,7 +53,7 @@ module Carson
53
53
 
54
54
  def self.build_parser
55
55
  OptionParser.new do |opts|
56
- opts.banner = "Usage: carson [status [--json]|setup|audit [--json]|sync|deliver [--merge] [--json] [--title T] [--body-file F]|prune [--all]|worktree create|done|remove <name>|onboard|refresh [--all]|offboard|template check|apply|review gate|sweep|govern [--dry-run] [--json] [--loop SECONDS]|version]"
56
+ opts.banner = "Usage: carson [status [--json]|setup|audit [--json]|sync [--json]|deliver [--merge] [--json] [--title T] [--body-file F]|prune [--all]|worktree create|done|remove <name>|onboard|refresh [--all]|offboard|template check|apply|review gate|sweep|govern [--dry-run] [--json] [--loop SECONDS]|version]"
57
57
  end
58
58
  end
59
59
 
@@ -90,6 +90,8 @@ module Carson
90
90
  parse_named_subcommand( command: command, usage: "gate|sweep", argv: argv, parser: parser, err: err )
91
91
  when "audit"
92
92
  parse_audit_command( argv: argv, err: err )
93
+ when "sync"
94
+ parse_sync_command( argv: argv, err: err )
93
95
  when "status"
94
96
  parse_status_command( argv: argv, err: err )
95
97
  when "deliver"
@@ -253,6 +255,15 @@ module Carson
253
255
  { command: "audit", json: json_flag }
254
256
  end
255
257
 
258
+ def self.parse_sync_command( argv:, err: )
259
+ json_flag = argv.delete( "--json" ) ? true : false
260
+ unless argv.empty?
261
+ err.puts "#{BADGE} Unexpected arguments for sync: #{argv.join( ' ' )}"
262
+ return { command: :invalid }
263
+ end
264
+ { command: "sync", json: json_flag }
265
+ end
266
+
256
267
  def self.parse_status_command( argv:, err: )
257
268
  json_flag = argv.delete( "--json" ) ? true : false
258
269
  unless argv.empty?
@@ -334,7 +345,7 @@ module Carson
334
345
  when "audit"
335
346
  runtime.audit!( json_output: parsed.fetch( :json, false ) )
336
347
  when "sync"
337
- runtime.sync!
348
+ runtime.sync!( json_output: parsed.fetch( :json, false ) )
338
349
  when "prune"
339
350
  runtime.prune!
340
351
  when "prune:all"
@@ -1,39 +1,83 @@
1
+ # Syncs local main branch with remote main.
2
+ # Supports --json for machine-readable structured output.
1
3
  module Carson
2
4
  class Runtime
3
5
  module Local
4
- def sync!
6
+ def sync!( json_output: false )
5
7
  fingerprint_status = block_if_outsider_fingerprints!
6
8
  return fingerprint_status unless fingerprint_status.nil?
7
9
 
8
10
  unless working_tree_clean?
9
- puts_line "BLOCK: working tree is dirty; commit/stash first, then run carson sync."
10
- return EXIT_BLOCK
11
+ return sync_finish(
12
+ result: { command: "sync", status: "block", error: "working tree is dirty", recovery: "git add -A && git commit, then carson sync" },
13
+ exit_code: EXIT_BLOCK, json_output: json_output
14
+ )
11
15
  end
12
16
  start_branch = current_branch
13
17
  switched = false
14
- git_system!( "fetch", config.git_remote, "--prune" )
18
+ sync_git!( "fetch", config.git_remote, "--prune", json_output: json_output )
15
19
  if start_branch != config.main_branch
16
- git_system!( "switch", config.main_branch )
20
+ sync_git!( "switch", config.main_branch, json_output: json_output )
17
21
  switched = true
18
22
  end
19
- git_system!( "pull", "--ff-only", config.git_remote, config.main_branch )
23
+ sync_git!( "pull", "--ff-only", config.git_remote, config.main_branch, json_output: json_output )
20
24
  ahead_count, behind_count, error_text = main_sync_counts
21
25
  if error_text
22
- puts_line "BLOCK: unable to verify main sync state (#{error_text})."
23
- return EXIT_BLOCK
26
+ return sync_finish(
27
+ result: { command: "sync", status: "block", error: "unable to verify main sync state (#{error_text})" },
28
+ exit_code: EXIT_BLOCK, json_output: json_output
29
+ )
24
30
  end
25
31
  if ahead_count.zero? && behind_count.zero?
26
- puts_line "OK: local #{config.main_branch} is now in sync with #{config.git_remote}/#{config.main_branch}."
27
- return EXIT_OK
32
+ return sync_finish(
33
+ result: { command: "sync", status: "ok", ahead: 0, behind: 0, main_branch: config.main_branch, remote: config.git_remote },
34
+ exit_code: EXIT_OK, json_output: json_output
35
+ )
28
36
  end
29
- puts_line "BLOCK: local #{config.main_branch} still diverges (ahead=#{ahead_count}, behind=#{behind_count})."
30
- EXIT_BLOCK
37
+ sync_finish(
38
+ result: { command: "sync", status: "block", ahead: ahead_count, behind: behind_count, main_branch: config.main_branch, remote: config.git_remote, error: "local #{config.main_branch} still diverges" },
39
+ exit_code: EXIT_BLOCK, json_output: json_output
40
+ )
31
41
  ensure
32
42
  git_system!( "switch", start_branch ) if switched && branch_exists?( branch_name: start_branch )
33
43
  end
34
44
 
35
45
  private
36
46
 
47
+ # Runs a git command, suppressing stdout/stderr in JSON mode to keep output clean.
48
+ def sync_git!( *args, json_output: false )
49
+ if json_output
50
+ _, stderr_text, success, = git_run( *args )
51
+ raise "git #{args.join( ' ' )} failed: #{stderr_text.to_s.strip}" unless success
52
+ else
53
+ git_system!( *args )
54
+ end
55
+ end
56
+
57
+ # Unified output for sync results — JSON or human-readable.
58
+ def sync_finish( result:, exit_code:, json_output: )
59
+ result[ :exit_code ] = exit_code
60
+
61
+ if json_output
62
+ out.puts JSON.pretty_generate( result )
63
+ else
64
+ print_sync_human( result: result )
65
+ end
66
+
67
+ exit_code
68
+ end
69
+
70
+ # Human-readable output for sync results.
71
+ def print_sync_human( result: )
72
+ if result[ :error ]
73
+ puts_line "BLOCK: #{result[ :error ]}."
74
+ puts_line " Recovery: #{result[ :recovery ]}" if result[ :recovery ]
75
+ return
76
+ end
77
+
78
+ puts_line "OK: local #{result[ :main_branch ]} is now in sync with #{result[ :remote ]}/#{result[ :main_branch ]}."
79
+ end
80
+
37
81
  # Returns ahead/behind counts for local main versus configured remote main.
38
82
  def main_sync_counts
39
83
  target = "#{config.main_branch}...#{config.git_remote}/#{config.main_branch}"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: carson
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.0
4
+ version: 3.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang