carson 3.0.0 → 3.1.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: 20f9db167969ae68e42a19efcc96c40108f4f3ab3f8b460369283457c9449e4b
4
- data.tar.gz: 7e94bf5adcafdbd532bd6e789304e04c2a555b7adcbce441363e681a27493c25
3
+ metadata.gz: 046c14a614687e668eae6d9f3df0eadbe02fcb103a1c93f25657791a0d72962c
4
+ data.tar.gz: 26ebca4203a0a9b3df1e38795580188bcf6951049304ae0eb4899deda35106f8
5
5
  SHA512:
6
- metadata.gz: 7bdc870e0ae003717b5e624d61386a41a89b5dcd20ec0e0c6fa5106b26791b53106bc43f763ca6199e79ca2bac8e76efcbff21d1ebbb126996550b009848f79f
7
- data.tar.gz: 19dce95ec2fd4fc5a89de09f8e26b814e6b077285159d425639e68e635eda195d52171c7e388ce27efd27b685f7486e2fc6f8fcf0e7005407cdf672bb9c9f7a2
6
+ metadata.gz: 38cb7ebf2e65293d5ea26bfb527314d86f7889949db033649fc49822b65feb7b7ea0e8fa5f35b6d959024ff4d280691432efde50dc15c37bf223665cd7d908b7
7
+ data.tar.gz: d05902ee1637919dd0a9781fa2de46e4370d2806df83c02c730584095f18811baea6168f3614d81900229dd5ec3c141ffb2ce6a40a8c338f233aa1a88a2a9b81
data/RELEASE.md CHANGED
@@ -5,6 +5,24 @@ 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.1.0 — Worktree Lifecycle
9
+
10
+ ### What changed
11
+
12
+ - **`carson worktree create <name>`** — creates a worktree under `.claude/worktrees/<name>` with a new branch based on main. One command, one result: path and branch name reported.
13
+ - **`carson worktree done <name>`** — marks a worktree as completed without deleting it. Verifies all changes are committed and pushed. Blocks with actionable guidance if uncommitted changes or unpushed commits exist. The worktree directory persists for batch cleanup later.
14
+ - **Deferred deletion model.** The full worktree lifecycle is now: create → work → done → batch cleanup. No worktree is deleted during an active session. Cleanup happens later via `carson worktree remove` or `carson housekeep`.
15
+
16
+ ### UX
17
+
18
+ - `carson worktree create` reports path and branch — ready to `cd` into immediately.
19
+ - `carson worktree done` gives recovery commands when changes are uncommitted or unpushed.
20
+ - Error messages across worktree subcommands now show `create|done|remove` in usage hints.
21
+
22
+ ### Migration
23
+
24
+ - No breaking changes. `carson worktree remove` continues to work as before.
25
+
8
26
  ## 3.0.0 — Agent-Oriented Carson
9
27
 
10
28
  ### Theme
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.0
1
+ 3.1.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 [--remote NAME] [--main-branch NAME] [--workflow STYLE] [--merge METHOD] [--canonical PATH]|audit|sync|prune [--all]|worktree remove <name-or-path>|onboard [repo_path]|refresh [--all|repo_path]|offboard [repo_path]|template check|template apply|review gate|review sweep|govern [--dry-run] [--json] [--loop SECONDS]|version]"
56
+ opts.banner = "Usage: carson [status [--json]|setup|audit|sync|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
 
@@ -170,12 +170,22 @@ module Carson
170
170
  def self.parse_worktree_subcommand( argv:, parser:, err: )
171
171
  action = argv.shift
172
172
  if action.to_s.strip.empty?
173
- err.puts "#{BADGE} Missing subcommand for worktree. Use: carson worktree remove <name-or-path>"
173
+ err.puts "#{BADGE} Missing subcommand for worktree. Use: carson worktree create|done|remove <name>"
174
174
  err.puts parser
175
175
  return { command: :invalid }
176
176
  end
177
177
 
178
178
  case action
179
+ when "create"
180
+ name = argv.shift
181
+ if name.to_s.strip.empty?
182
+ err.puts "#{BADGE} Missing name for worktree create. Use: carson worktree create <name>"
183
+ return { command: :invalid }
184
+ end
185
+ { command: "worktree:create", worktree_name: name }
186
+ when "done"
187
+ name = argv.shift
188
+ { command: "worktree:done", worktree_name: name }
179
189
  when "remove"
180
190
  force = argv.delete( "--force" ) ? true : false
181
191
  worktree_path = argv.shift
@@ -185,7 +195,7 @@ module Carson
185
195
  end
186
196
  { command: "worktree:remove", worktree_path: worktree_path, force: force }
187
197
  else
188
- err.puts "#{BADGE} Unknown worktree subcommand: #{action}. Use: carson worktree remove <name-or-path>"
198
+ err.puts "#{BADGE} Unknown worktree subcommand: #{action}. Use: carson worktree create|done|remove <name>"
189
199
  { command: :invalid }
190
200
  end
191
201
  end
@@ -289,6 +299,10 @@ module Carson
289
299
  runtime.prune!
290
300
  when "prune:all"
291
301
  runtime.prune_all!
302
+ when "worktree:create"
303
+ runtime.worktree_create!( name: parsed.fetch( :worktree_name ) )
304
+ when "worktree:done"
305
+ runtime.worktree_done!( name: parsed.fetch( :worktree_name, nil ) )
292
306
  when "worktree:remove"
293
307
  runtime.worktree_remove!( worktree_path: parsed.fetch( :worktree_path ), force: parsed.fetch( :force, false ) )
294
308
  when "onboard"
@@ -2,7 +2,83 @@ module Carson
2
2
  class Runtime
3
3
  module Local
4
4
  # Safe worktree lifecycle management for coding agents.
5
- # Enforces the teardown order: exit worktree git worktree remove branch cleanup.
5
+ # Three operations: create, done (mark completed), remove (batch cleanup).
6
+ # The deferred deletion model: worktrees persist after use, cleaned up later.
7
+
8
+ # Creates a new worktree under .claude/worktrees/<name> with a fresh branch.
9
+ def worktree_create!( name: )
10
+ worktrees_dir = File.join( repo_root, ".claude", "worktrees" )
11
+ wt_path = File.join( worktrees_dir, name )
12
+
13
+ if Dir.exist?( wt_path )
14
+ puts_line "ERROR: worktree already exists: #{name}"
15
+ puts_line " Path: #{wt_path}"
16
+ return EXIT_ERROR
17
+ end
18
+
19
+ # Determine the base branch (main branch from config).
20
+ base = config.main_branch
21
+
22
+ # Create the worktree with a new branch based on the main branch.
23
+ FileUtils.mkdir_p( worktrees_dir )
24
+ _, wt_stderr, wt_success, = git_run( "worktree", "add", wt_path, "-b", name, base )
25
+ unless wt_success
26
+ error_text = wt_stderr.to_s.strip
27
+ error_text = "unable to create worktree" if error_text.empty?
28
+ puts_line "ERROR: #{error_text}"
29
+ return EXIT_ERROR
30
+ end
31
+
32
+ puts_line "Worktree created: #{name}"
33
+ puts_line " Path: #{wt_path}"
34
+ puts_line " Branch: #{name}"
35
+ EXIT_OK
36
+ end
37
+
38
+ # Marks a worktree as completed without deleting it.
39
+ # Verifies all changes are committed. Deferred deletion — cleanup happens later.
40
+ def worktree_done!( name: nil )
41
+ if name.to_s.strip.empty?
42
+ # Try to detect current worktree from CWD.
43
+ puts_line "ERROR: missing worktree name. Use: carson worktree done <name>"
44
+ return EXIT_ERROR
45
+ end
46
+
47
+ resolved_path = resolve_worktree_path( worktree_path: name )
48
+
49
+ unless worktree_registered?( path: resolved_path )
50
+ puts_line "ERROR: #{name} is not a registered worktree."
51
+ return EXIT_ERROR
52
+ end
53
+
54
+ # Check for uncommitted changes in the worktree.
55
+ wt_status, _, status_success, = Open3.capture3( "git", "status", "--porcelain", chdir: resolved_path )
56
+ if status_success && !wt_status.strip.empty?
57
+ puts_line "Worktree has uncommitted changes: #{name}"
58
+ puts_line " Commit your changes first, then run `carson worktree done #{name}` again."
59
+ return EXIT_BLOCK
60
+ end
61
+
62
+ # Check for unpushed commits.
63
+ branch = worktree_branch( path: resolved_path )
64
+ if branch
65
+ remote = config.git_remote
66
+ remote_ref = "#{remote}/#{branch}"
67
+ ahead, _, ahead_ok, = Open3.capture3( "git", "rev-list", "--count", "#{remote_ref}..#{branch}", chdir: resolved_path )
68
+ if ahead_ok && ahead.strip.to_i > 0
69
+ puts_line "Worktree has unpushed commits: #{name}"
70
+ puts_line " Push with `git -C #{resolved_path} push #{remote} #{branch}` first."
71
+ return EXIT_BLOCK
72
+ end
73
+ end
74
+
75
+ puts_line "Worktree done: #{name}"
76
+ puts_line " Branch: #{branch || '(detached)'}"
77
+ puts_line " Cleanup later with `carson worktree remove #{name}` or `carson housekeep`."
78
+ EXIT_OK
79
+ end
80
+
81
+ # Removes a worktree: directory, git registration, and branch.
6
82
  # Never forces removal — if the worktree has uncommitted changes, refuses unless
7
83
  # the user explicitly passes force: true via CLI --force flag.
8
84
  def worktree_remove!( worktree_path:, force: false )
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.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hailei Wang