sxn 0.2.2 → 0.2.5

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: b9758f35bc9f122c019b15dd8a46f712cda58eb86495c6e5bb8c63791b10525e
4
- data.tar.gz: 628449e55edf8b33ade3f83cdfab4369234bd22e26e5625cc3890b3c46a74702
3
+ metadata.gz: 30ceb6fb94a023b31e9b9c4c4354806a5343b7cefa8f1b43047011f86e3048cf
4
+ data.tar.gz: 071b888de74d9e284376acf2ed425a5a33ddf329ac01112bc3fbc65acc8dd363
5
5
  SHA512:
6
- metadata.gz: 7970fbb77d1b7b21da3b93d566bb54a42219c787ca081b2319ec6c3dcf7b31dee35761339da42322ac2acf393232fa458325e3573dfebec546caa2e14f9da0eb
7
- data.tar.gz: bd4f880ea4b2aa55b1a0de1d87d6e6bcc41a787218d29c104c644c87d07d2c43048b77217ba6ceae3f3df66a2da63c531a6b7f6cc96781d1454f0a2622232d1a
6
+ metadata.gz: 93cb3f85f80ccc13ffcc62662fa86bd0e8b7531105ab08dc49f0213744a858bbaf7fff8823c2d5358617704eeb5aae6f2264ba02f20e14c71003fd8e912136d4
7
+ data.tar.gz: fc1e076616d354218525d2d8ee6a6454a04dfd5d1db969f7032792c91ec1679605a4d0351573a57cd3b5af64c8d06fdecad1068b51dfd66bc2bea5c92bc52dfc
data/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.5] - 2025-11-30
9
+
10
+ ### Added
11
+ - `sxn enter` command to quickly navigate to current session directory
12
+ - `sxn current enter` subcommand as alternative way to enter session
13
+ - `--path` option for `sxn current` to output only the session path
14
+ - `sxn shell` command to install shell integration (idempotent)
15
+ - Auto-detects shell type (bash/zsh)
16
+ - Installs `sxn-enter` function to shell config
17
+ - Supports `--uninstall` to remove integration
18
+ - Supports `--shell-type` to specify shell explicitly
19
+
20
+ ## [0.2.4] - 2025-11-30
21
+
22
+ ### Added
23
+ - Interactive worktree wizard after session creation
24
+ - Prompts to add worktrees with descriptive explanations
25
+ - Supports adding multiple worktrees in sequence
26
+ - Explains branch options including remote tracking syntax
27
+ - `--skip-worktree` flag to bypass the wizard when creating sessions
28
+ - `--verbose` flag for worktree debugging with detailed git output
29
+
30
+ ### Changed
31
+ - Sessions now automatically switch to newly created session (no need to run `sxn use` afterwards)
32
+ - Improved project manager to safely handle nil projects configuration
33
+
34
+ ### Fixed
35
+ - Fixed test mocks for verbose parameter in worktree operations
36
+ - Fixed version spec to support semver pre-release format
37
+
38
+ ## [0.2.3] - 2025-09-16
39
+
40
+ ### Added
41
+ - Smart branch defaults: worktrees now use session name as default branch
42
+ - Remote branch tracking with `remote:` prefix syntax (e.g., `sxn worktree add project remote:origin/feature`)
43
+ - Automatic orphaned worktree recovery and cleanup
44
+ - Enhanced error messages with actionable suggestions
45
+
46
+ ### Changed
47
+ - Improved worktree creation logic to handle existing/orphaned states
48
+ - Better error handling for remote branch operations
49
+ - Updated CLI documentation with new branch options
50
+
51
+ ### Fixed
52
+ - Orphaned worktree cleanup now works for both existing and missing directories
53
+ - Worktree creation properly handles branch conflicts
54
+ - Test suite compatibility with new worktree features
55
+
8
56
  ## [0.2.1] - 2025-01-20
9
57
 
10
58
  ### Fixed
@@ -53,5 +101,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
53
101
  - Initial placeholder release
54
102
  - Basic gem structure
55
103
 
104
+ [0.2.4]: https://github.com/idl3/sxn/compare/v0.2.3...v0.2.4
105
+ [0.2.3]: https://github.com/idl3/sxn/compare/v0.2.1...v0.2.3
106
+ [0.2.1]: https://github.com/idl3/sxn/compare/v0.2.0...v0.2.1
56
107
  [0.2.0]: https://github.com/idl3/sxn/compare/v0.1.0...v0.2.0
57
108
  [0.1.0]: https://github.com/idl3/sxn/releases/tag/v0.1.0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- sxn (0.2.2)
4
+ sxn (0.2.5)
5
5
  async (~> 2.0)
6
6
  bcrypt (~> 3.1)
7
7
  dry-configurable (~> 1.0)
data/README.md CHANGED
@@ -1,10 +1,10 @@
1
- # Sxn
1
+ # sxn
2
2
 
3
3
  [![CI](https://github.com/idl3/sxn/actions/workflows/ci.yml/badge.svg)](https://github.com/idl3/sxn/actions/workflows/ci.yml)
4
4
  [![Ruby Version](https://img.shields.io/badge/ruby-3.2%2B-red)](https://www.ruby-lang.org)
5
5
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE.txt)
6
6
 
7
- Sxn is a powerful session management tool for multi-repository development. It helps developers manage complex development environments with multiple git repositories, providing isolated workspaces, automatic project setup, and intelligent session management.
7
+ sxn is a powerful session management tool for multi-repository development. It helps developers manage complex development environments with multiple git repositories, providing isolated workspaces, automatic project setup, and intelligent session management.
8
8
 
9
9
  ## Features
10
10
 
@@ -38,7 +38,7 @@ gem install sxn
38
38
 
39
39
  ## Quick Start
40
40
 
41
- ### Initialize Sxn in your workspace
41
+ ### Initialize sxn in your workspace
42
42
 
43
43
  ```bash
44
44
  sxn init
@@ -135,7 +135,7 @@ sxn rules apply my-app
135
135
 
136
136
  ## Configuration
137
137
 
138
- Sxn stores its configuration in `.sxn/config.yml` in your workspace:
138
+ sxn stores its configuration in `.sxn/config.yml` in your workspace:
139
139
 
140
140
  ```yaml
141
141
  sessions_folder: .sxn-sessions
@@ -168,7 +168,7 @@ rules:
168
168
 
169
169
  ## Templates
170
170
 
171
- Sxn includes templates for common project types:
171
+ sxn includes templates for common project types:
172
172
 
173
173
  - **Rails**: CLAUDE.md, database.yml, session-info.md
174
174
  - **JavaScript**: README.md, session-info.md
@@ -178,19 +178,87 @@ Templates use Liquid syntax and have access to session, project, and environment
178
178
 
179
179
  ## Development
180
180
 
181
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
181
+ ### Setup
182
182
 
183
- ### Running Tests
183
+ After cloning the repository, run:
184
+
185
+ ```bash
186
+ bundle install
187
+ ./script/setup-hooks # Set up git hooks for automated checks
188
+ ```
189
+
190
+ ### Testing and Code Quality
191
+
192
+ **Important**: All code must pass tests and linting before being pushed to the repository.
193
+
194
+ #### Running Tests
184
195
 
185
196
  ```bash
186
197
  # Run all tests
187
198
  bundle exec rspec
188
199
 
200
+ # Run tests in parallel (faster)
201
+ bundle exec rake parallel:spec
202
+
203
+ # Run with coverage report
204
+ bundle exec rake parallel:spec_with_coverage
205
+ ```
206
+
207
+ #### Code Style
208
+
209
+ ```bash
210
+ # Check code style
211
+ bundle exec rubocop
212
+
213
+ # Auto-fix code style issues
214
+ bundle exec rubocop -a
215
+ ```
216
+
217
+ #### Git Hooks
218
+
219
+ The project includes a pre-push hook that automatically runs RuboCop and RSpec before allowing pushes. To set it up:
220
+
221
+ ```bash
222
+ ./script/setup-hooks
223
+ ```
224
+
225
+ To bypass hooks in emergency situations (not recommended):
226
+ ```bash
227
+ git push --no-verify
228
+ ```
229
+
230
+ ### Development Workflow
231
+
232
+ 1. Make your changes
233
+ 2. Run `bundle exec rubocop -a` to fix any style issues
234
+ 3. Run `bundle exec rspec` to ensure tests pass
235
+ 4. Commit your changes
236
+ 5. Push (pre-push hooks will run automatically)
237
+
238
+ ### Contributing
239
+
240
+ 1. Fork the repository
241
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
242
+ 3. Make your changes
243
+ 4. Ensure tests pass and code style is correct
244
+ 5. Commit your changes with meaningful commit messages
245
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
246
+ 7. Open a Pull Request
247
+
248
+ ### Additional Testing Options
249
+
250
+ ```bash
251
+ # Run all tests in parallel (recommended for speed)
252
+ bundle exec parallel_rspec spec/
253
+
254
+ # Run all tests sequentially
255
+ bundle exec rspec
256
+
189
257
  # Run only unit tests
190
258
  bundle exec rspec spec/unit
191
259
 
192
260
  # Run with coverage
193
- ENABLE_SIMPLECOV=true bundle exec rspec
261
+ ENABLE_SIMPLECOV=true bundle exec parallel_rspec spec/
194
262
  ```
195
263
 
196
264
  ### Type Checking
data/lib/sxn/CLI.rb CHANGED
@@ -41,8 +41,11 @@ module Sxn
41
41
  desc "add SESSION_NAME", "Create a new session (shortcut for 'sxn sessions add')"
42
42
  option :description, type: :string, aliases: "-d", desc: "Session description"
43
43
  option :linear_task, type: :string, aliases: "-l", desc: "Linear task ID"
44
+ option :branch, type: :string, aliases: "-b", desc: "Default branch for worktrees"
44
45
  def add(session_name)
45
- Commands::Sessions.new.add(session_name)
46
+ cmd = Commands::Sessions.new
47
+ cmd.options = options
48
+ cmd.add(session_name)
46
49
  rescue Sxn::Error => e
47
50
  handle_error(e)
48
51
  end
@@ -62,10 +65,102 @@ module Sxn
62
65
  handle_error(e)
63
66
  end
64
67
 
65
- desc "current", "Show current session (shortcut for 'sxn sessions current')"
68
+ desc "current [SUBCOMMAND]", "Show current session (shortcut for 'sxn sessions current')"
66
69
  option :verbose, type: :boolean, aliases: "-v", desc: "Show detailed information"
67
- def current
68
- Commands::Sessions.new.current
70
+ option :path, type: :boolean, aliases: "-p", desc: "Output only the session path"
71
+ def current(subcommand = nil)
72
+ Commands::Sessions.new.current(subcommand)
73
+ rescue Sxn::Error => e
74
+ handle_error(e)
75
+ end
76
+
77
+ desc "enter", "Enter current session directory (outputs cd command for shell eval)"
78
+ long_desc <<-LONGDESC
79
+ Outputs a cd command to navigate to the current session directory.
80
+
81
+ Usage with shell eval:
82
+ eval "$(sxn enter)"
83
+
84
+ Or install shell integration for easier use:
85
+ sxn shell
86
+
87
+ Then simply run:
88
+ sxn-enter
89
+ LONGDESC
90
+ def enter
91
+ Commands::Sessions.new.enter
92
+ rescue Sxn::Error => e
93
+ handle_error(e)
94
+ end
95
+
96
+ desc "up", "Navigate to project root from session (outputs cd command for shell eval)"
97
+ long_desc <<-LONGDESC
98
+ Outputs a cd command to navigate to the project root from within a session.
99
+
100
+ The project root is determined from the .sxnrc file in the session directory,
101
+ which points back to the parent .sxn folder.
102
+
103
+ Usage with shell eval:
104
+ eval "$(sxn up)"
105
+
106
+ Or install shell integration for easier use:
107
+ sxn shell
108
+
109
+ Then simply run:
110
+ sxn-up
111
+ LONGDESC
112
+ def up
113
+ require "shellwords"
114
+
115
+ session_config = Sxn::Core::SessionConfig.find_from_path(Dir.pwd)
116
+
117
+ unless session_config
118
+ warn "Not in a session directory."
119
+ warn ""
120
+ warn "This command works when run from within a session folder."
121
+ warn "Session folders contain a .sxnrc file that points back to the project."
122
+ warn ""
123
+ warn "Tip: Add this function to your shell profile for easier navigation:"
124
+ warn ""
125
+ warn " sxn-up() { eval \"$(sxn up 2>/dev/null)\" || sxn up; }"
126
+ exit(1)
127
+ end
128
+
129
+ project_root = session_config.project_root
130
+
131
+ unless project_root && File.directory?(project_root)
132
+ warn "Could not determine project root from .sxnrc"
133
+ warn "parent_sxn_path: #{session_config.parent_sxn_path || "nil"}"
134
+ exit(1)
135
+ end
136
+
137
+ # Output the cd command for shell integration
138
+ puts "cd #{Shellwords.escape(project_root)}"
139
+ rescue Sxn::Error => e
140
+ handle_error(e)
141
+ end
142
+
143
+ desc "shell", "Install shell integration (sxn-enter function)"
144
+ option :shell_type, type: :string, enum: %w[bash zsh auto], default: "auto",
145
+ desc: "Shell type (bash, zsh, or auto-detect)"
146
+ option :uninstall, type: :boolean, default: false, desc: "Remove shell integration"
147
+ long_desc <<-LONGDESC
148
+ Installs shell integration to your shell configuration file (.zshrc or .bashrc).
149
+
150
+ This adds the sxn-enter function which allows you to quickly navigate
151
+ to your current session directory.
152
+
153
+ The installation is idempotent - running it multiple times will not
154
+ add duplicate entries.
155
+
156
+ Examples:
157
+ sxn shell # Auto-detect shell and install
158
+ sxn shell --shell-type=zsh # Install for zsh specifically
159
+ sxn shell --uninstall # Remove shell integration
160
+ LONGDESC
161
+ map "shell" => :install_shell_wrapper
162
+ def install_shell_wrapper
163
+ Commands::Init.new.invoke(:install_shell, [], options)
69
164
  rescue Sxn::Error => e
70
165
  handle_error(e)
71
166
  end
@@ -8,6 +8,34 @@ module Sxn
8
8
  class Init < Thor
9
9
  include Thor::Actions
10
10
 
11
+ # Shell integration marker - used to identify sxn shell functions
12
+ SHELL_MARKER = "# sxn shell integration"
13
+ SHELL_MARKER_END = "# end sxn shell integration"
14
+
15
+ # Shell function that gets installed
16
+ SHELL_FUNCTION = <<~SHELL.freeze
17
+ #{SHELL_MARKER}
18
+ sxn-enter() {
19
+ local cmd
20
+ cmd="$(sxn enter 2>/dev/null)"
21
+ if [ $? -eq 0 ] && [ -n "$cmd" ]; then
22
+ eval "$cmd"
23
+ else
24
+ sxn enter
25
+ fi
26
+ }
27
+ sxn-up() {
28
+ local cmd
29
+ cmd="$(sxn up 2>/dev/null)"
30
+ if [ $? -eq 0 ] && [ -n "$cmd" ]; then
31
+ eval "$cmd"
32
+ else
33
+ sxn up
34
+ fi
35
+ }
36
+ #{SHELL_MARKER_END}
37
+ SHELL
38
+
11
39
  desc "init [FOLDER]", "Initialize sxn in a project folder"
12
40
  option :force, type: :boolean, desc: "Force initialization even if already initialized"
13
41
  option :auto_detect, type: :boolean, default: true, desc: "Automatically detect and register projects"
@@ -55,8 +83,116 @@ module Sxn
55
83
  end
56
84
  end
57
85
 
86
+ desc "install_shell", "Install shell integration (sxn-enter function)"
87
+ option :shell_type, type: :string, enum: %w[bash zsh auto], default: "auto",
88
+ desc: "Shell type (bash, zsh, or auto-detect)"
89
+ option :uninstall, type: :boolean, default: false, desc: "Remove shell integration"
90
+ def install_shell
91
+ @ui.section("Shell Integration")
92
+
93
+ shell_type = detect_shell_type
94
+ rc_file = shell_rc_file(shell_type)
95
+
96
+ unless rc_file
97
+ @ui.error("Could not determine shell configuration file")
98
+ @ui.info("Supported shells: bash, zsh")
99
+ exit(1)
100
+ end
101
+
102
+ if options[:uninstall]
103
+ uninstall_shell_integration(rc_file, shell_type)
104
+ else
105
+ install_shell_integration(rc_file, shell_type)
106
+ end
107
+ end
108
+
58
109
  private
59
110
 
111
+ def detect_shell_type
112
+ shell_opt = options[:shell_type] || options[:shell] || "auto"
113
+ return shell_opt unless shell_opt == "auto"
114
+
115
+ # Check SHELL environment variable
116
+ current_shell = ENV.fetch("SHELL", "")
117
+ if current_shell.include?("zsh")
118
+ "zsh"
119
+ elsif current_shell.include?("bash")
120
+ "bash"
121
+ else
122
+ # Default to bash
123
+ "bash"
124
+ end
125
+ end
126
+
127
+ def shell_rc_file(shell_type)
128
+ home = Dir.home
129
+ case shell_type
130
+ when "zsh"
131
+ File.join(home, ".zshrc")
132
+ when "bash"
133
+ # Prefer .bashrc, fall back to .bash_profile on macOS
134
+ bashrc = File.join(home, ".bashrc")
135
+ bash_profile = File.join(home, ".bash_profile")
136
+ File.exist?(bashrc) ? bashrc : bash_profile
137
+ end
138
+ end
139
+
140
+ def shell_integration_installed?(rc_file)
141
+ return false unless File.exist?(rc_file)
142
+
143
+ content = File.read(rc_file)
144
+ content.include?(SHELL_MARKER)
145
+ end
146
+
147
+ def install_shell_integration(rc_file, _shell_type)
148
+ if shell_integration_installed?(rc_file)
149
+ @ui.info("Shell integration already installed in #{rc_file}")
150
+ @ui.info("Use --uninstall to remove it first if you want to reinstall")
151
+ return
152
+ end
153
+
154
+ # Ensure rc file exists
155
+ FileUtils.touch(rc_file) unless File.exist?(rc_file)
156
+
157
+ # Append shell function
158
+ File.open(rc_file, "a") do |f|
159
+ f.puts "" # Add blank line before
160
+ f.puts SHELL_FUNCTION
161
+ end
162
+
163
+ @ui.success("Installed shell integration to #{rc_file}")
164
+ @ui.newline
165
+ @ui.info("The following functions were added:")
166
+ @ui.newline
167
+ puts " sxn-enter - Navigate to current session directory"
168
+ puts " sxn-up - Navigate to project root from session"
169
+ @ui.newline
170
+ @ui.recovery_suggestion("Run 'source #{rc_file}' or restart your shell to use them")
171
+ end
172
+
173
+ def uninstall_shell_integration(rc_file, _shell_type)
174
+ unless File.exist?(rc_file)
175
+ @ui.info("Shell configuration file not found: #{rc_file}")
176
+ return
177
+ end
178
+
179
+ unless shell_integration_installed?(rc_file)
180
+ @ui.info("Shell integration not installed in #{rc_file}")
181
+ return
182
+ end
183
+
184
+ # Read file and remove sxn block
185
+ content = File.read(rc_file)
186
+ # Remove the block between markers (including blank line before)
187
+ pattern = /\n?#{Regexp.escape(SHELL_MARKER)}.*?#{Regexp.escape(SHELL_MARKER_END)}\n?/m
188
+ new_content = content.gsub(pattern, "\n")
189
+
190
+ File.write(rc_file, new_content)
191
+
192
+ @ui.success("Removed shell integration from #{rc_file}")
193
+ @ui.recovery_suggestion("Run 'source #{rc_file}' or restart your shell")
194
+ end
195
+
60
196
  def determine_sessions_folder(folder)
61
197
  return folder if folder && !options[:quiet]
62
198