claude-worktree 0.1.3 → 0.1.4

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: 8f961f02caebcef789ed77e043a5390b49c50397e87d21588e74e0c17417a6da
4
- data.tar.gz: 91657ce429ea01a43424da72d87f71f9964a8da7e419ebb07fe59c30269c3a6d
3
+ metadata.gz: aa2000361072768df44df230f654d22c5bd8e435bceb02bb597e7bbbfc369b28
4
+ data.tar.gz: 7c42b53349ffcb6dc3050c68db40b869da936d94c61f4cc25ac7899adbfcdc66
5
5
  SHA512:
6
- metadata.gz: a0b74a2251dc6c8ff2101179ce96d87045a86198065a86a90f8c14e6da2f86a75377824a6388c68cd20ecdb2330c25dc962d35557cce8c66b318ea329a9a0aaa
7
- data.tar.gz: eb1e06bd91998c652e2694a42c3ae664fd0052c90a0587a84f8fe1fc636d232dcbef6b91d55a04fd2d2efff6c5ade71cff4851479d304388c8303b918770a89e
6
+ metadata.gz: c2a27c69c6d1117cadefc144e85b04816580f58acd621ce036a69a64f54200c1705c564ed11df4b481ed7d552b1554c10d8a2e7d1dfbb3e7003eb697f812a277
7
+ data.tar.gz: 78601cef3ca323a17c8b9333d54405209b7470545c86c15e13b5518cc17764d2edf46ca2b072fc6df4245bdbd47040924fb3fcc23e4ddbacd51e8114467a2fec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.4] - 2026-01-30
4
+
5
+ ### Added
6
+ - **Permanent CD on exit**: After quitting cwt, your shell stays in the last resumed worktree directory
7
+ - **Visible setup output**: `.cwt/setup` script now runs with visible output on first resume (not during worktree creation)
8
+ - **Teardown support**: Optional `.cwt/teardown` script runs before worktree deletion
9
+ - **CWT_ROOT environment variable**: Setup and teardown scripts receive `$CWT_ROOT` pointing to the repo root
10
+ - Integration tests for setup/teardown functionality
11
+ - Homebrew update instructions in deploy script
12
+
13
+ ### Changed
14
+ - Setup now runs on first resume instead of during worktree creation
15
+ - Setup only runs once per worktree (tracked via `.cwt_needs_setup` marker)
16
+
3
17
  ## [0.1.0] - 2026-01-29
4
18
 
5
19
  - Initial release
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Claude
4
4
  module Worktree
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
data/lib/cwt/app.rb CHANGED
@@ -52,7 +52,7 @@ module Cwt
52
52
  end
53
53
 
54
54
  event = tui.poll_event(timeout: 0.1)
55
-
55
+
56
56
  # Process TUI Event
57
57
  cmd = nil
58
58
  if event.key?
@@ -62,7 +62,7 @@ module Cwt
62
62
  elsif event.none?
63
63
  cmd = Update.handle(model, { type: :tick })
64
64
  end
65
-
65
+
66
66
  handle_command(cmd, model, tui, main_queue) if cmd
67
67
 
68
68
  # Process Background Queue
@@ -74,6 +74,12 @@ module Cwt
74
74
  end
75
75
  end
76
76
  end
77
+
78
+ # After TUI exits, cd into last worktree if one was resumed
79
+ if model.exit_directory && Dir.exist?(model.exit_directory)
80
+ Dir.chdir(model.exit_directory)
81
+ exec ENV.fetch('SHELL', '/bin/zsh')
82
+ end
77
83
  end
78
84
 
79
85
  def self.handle_command(cmd, model, tui, main_queue)
@@ -88,11 +94,18 @@ module Cwt
88
94
  case cmd[:type]
89
95
  when :quit
90
96
  model.quit
91
- when :create_worktree, :delete_worktree, :refresh_list
97
+ when :delete_worktree
98
+ # Suspend TUI for visible teardown output
99
+ RatatuiRuby.restore_terminal
100
+ puts "\e[H\e[2J" # Clear screen
101
+ result = Update.handle(model, cmd)
102
+ RatatuiRuby.init_terminal
103
+ handle_command(result, model, tui, main_queue)
104
+ when :create_worktree, :refresh_list
92
105
  result = Update.handle(model, cmd)
93
106
  handle_command(result, model, tui, main_queue)
94
107
  when :resume_worktree, :suspend_and_resume
95
- suspend_tui_and_run(cmd[:path], tui)
108
+ suspend_tui_and_run(cmd[:path], model, tui)
96
109
  Update.refresh_list(model)
97
110
  start_background_fetch(model, main_queue)
98
111
  end
@@ -134,11 +147,24 @@ module Cwt
134
147
  end
135
148
  end
136
149
 
137
- def self.suspend_tui_and_run(path, tui)
150
+ def self.suspend_tui_and_run(path, model, tui)
138
151
  RatatuiRuby.restore_terminal
139
-
152
+
140
153
  puts "\e[H\e[2J" # Clear screen
141
- puts "Resuming session in #{path}..."
154
+
155
+ # Run setup if this is a new worktree
156
+ if Git.needs_setup?(path)
157
+ begin
158
+ Git.run_setup_visible(path)
159
+ Git.mark_setup_complete(path)
160
+ rescue Interrupt
161
+ puts "\nSetup aborted."
162
+ RatatuiRuby.init_terminal
163
+ return
164
+ end
165
+ end
166
+
167
+ puts "Launching claude in #{path}..."
142
168
  begin
143
169
  Dir.chdir(path) do
144
170
  if defined?(Bundler)
@@ -147,6 +173,8 @@ module Cwt
147
173
  system("claude")
148
174
  end
149
175
  end
176
+ # Track last resumed path for exit
177
+ model.exit_directory = path
150
178
  rescue => e
151
179
  puts "Error: #{e.message}"
152
180
  print "Press any key to return..."
data/lib/cwt/git.rb CHANGED
@@ -49,6 +49,8 @@ module Cwt
49
49
  { dirty: false }
50
50
  end
51
51
 
52
+ SETUP_MARKER = ".cwt_needs_setup"
53
+
52
54
  def self.add_worktree(name)
53
55
  # Sanitize name
54
56
  safe_name = name.strip.gsub(/[^a-zA-Z0-9_\-]/, '_')
@@ -66,13 +68,81 @@ module Cwt
66
68
  return { success: false, error: stderr }
67
69
  end
68
70
 
69
- # Post-creation setup: Copy .env if it exists
70
- setup_environment(path)
71
+ # Mark worktree as needing setup (will run on first resume)
72
+ mark_needs_setup(path)
71
73
 
72
74
  { success: true, path: path }
73
75
  end
74
76
 
77
+ def self.needs_setup?(path)
78
+ File.exist?(File.join(path, SETUP_MARKER))
79
+ end
80
+
81
+ def self.mark_needs_setup(path)
82
+ FileUtils.touch(File.join(path, SETUP_MARKER))
83
+ end
84
+
85
+ def self.mark_setup_complete(path)
86
+ marker = File.join(path, SETUP_MARKER)
87
+ File.delete(marker) if File.exist?(marker)
88
+ end
89
+
90
+ def self.run_setup_visible(path)
91
+ root = Dir.pwd
92
+ setup_script = File.join(root, ".cwt", "setup")
93
+
94
+ if File.exist?(setup_script) && File.executable?(setup_script)
95
+ puts "\e[1;36m=== Running .cwt/setup ===\e[0m"
96
+ puts
97
+
98
+ success = Dir.chdir(path) do
99
+ system({ "CWT_ROOT" => root }, setup_script)
100
+ end
101
+
102
+ puts
103
+
104
+ unless success
105
+ puts "\e[1;33mWarning: .cwt/setup failed (exit code: #{$?.exitstatus})\e[0m"
106
+ print "Press Enter to continue or Ctrl+C to abort..."
107
+ begin
108
+ STDIN.gets
109
+ rescue Interrupt
110
+ raise
111
+ end
112
+ end
113
+ else
114
+ # Default behavior: Symlink .env and node_modules (silent, fast)
115
+ setup_default_symlinks(path, root)
116
+ end
117
+ end
118
+
119
+ def self.run_teardown(path)
120
+ root = Dir.pwd
121
+ teardown_script = File.join(root, ".cwt", "teardown")
122
+
123
+ return { ran: false } unless File.exist?(teardown_script) && File.executable?(teardown_script)
124
+
125
+ puts "\e[1;36m=== Running .cwt/teardown ===\e[0m"
126
+ puts
127
+
128
+ success = Dir.chdir(path) do
129
+ system({ "CWT_ROOT" => root }, teardown_script)
130
+ end
131
+
132
+ puts
133
+
134
+ { ran: true, success: success }
135
+ end
136
+
75
137
  def self.remove_worktree(path, force: false)
138
+ # Step 0: Run teardown script if directory exists
139
+ if Dir.exist?(path)
140
+ result = run_teardown(path)
141
+ if result[:ran] && !result[:success] && !force
142
+ return { success: false, error: "Teardown script failed. Use 'D' to force delete." }
143
+ end
144
+ end
145
+
76
146
  # Step 1: Cleanup symlinks/copies (Best effort)
77
147
  # This helps 'safe delete' succeed if only untracked files are present.
78
148
  [".env", "node_modules"].each do |file|
@@ -153,19 +223,7 @@ module Cwt
153
223
  worktrees
154
224
  end
155
225
 
156
- def self.setup_environment(target_path)
157
- root = Dir.pwd
158
- setup_script = File.join(root, ".cwt", "setup")
159
-
160
- # 1. Custom Setup Script
161
- if File.exist?(setup_script) && File.executable?(setup_script)
162
- # Execute the script inside the new worktree
163
- # passing the root path as an argument might be helpful, but relying on relative paths is standard.
164
- Open3.capture2(setup_script, chdir: target_path)
165
- return
166
- end
167
-
168
- # 2. Default Behavior: Symlink .env and node_modules
226
+ def self.setup_default_symlinks(target_path, root)
169
227
  files_to_link = [".env", "node_modules"]
170
228
 
171
229
  files_to_link.each do |file|
data/lib/cwt/model.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  module Cwt
2
2
  class Model
3
3
  attr_reader :worktrees, :selection_index, :mode, :input_buffer, :message, :running, :fetch_generation, :filter_query
4
+ attr_accessor :exit_directory
4
5
 
5
6
  def initialize
6
7
  @worktrees = []
@@ -11,6 +12,7 @@ module Cwt
11
12
  @message = "Welcome to CWT"
12
13
  @running = true
13
14
  @fetch_generation = 0
15
+ @exit_directory = nil
14
16
  end
15
17
 
16
18
  def update_worktrees(list)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: claude-worktree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Garcia