sxn 0.2.1 → 0.2.3
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 +4 -4
- data/.parallel_rspec +2 -0
- data/.parallel_rspec_options +3 -0
- data/.rspec +2 -1
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -1
- data/Gemfile.lock +4 -1
- data/README.md +80 -8
- data/Rakefile +41 -0
- data/docs/parallel_testing.md +124 -0
- data/lib/sxn/CLI.rb +28 -16
- data/lib/sxn/commands/sessions.rb +18 -3
- data/lib/sxn/commands/worktrees.rb +23 -4
- data/lib/sxn/core/config_manager.rb +13 -1
- data/lib/sxn/core/worktree_manager.rb +103 -4
- data/lib/sxn/database/session_database.rb +56 -6
- data/lib/sxn/runtime_validations.rb +5 -1
- data/lib/sxn/version.rb +1 -1
- data/script/setup-hooks +52 -0
- data/sig/sxn/cli.rbs +6 -0
- data/sig/sxn/commands/sessions.rbs +4 -0
- metadata +5 -2
- data/sxn.gemspec +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6c2adba72c82bfd3b3b92c29bf497cc0c486b16261edc38c103135a7391525a4
|
4
|
+
data.tar.gz: fa079e0580d51496bf4d5f4af242b291847854a1d6dd6f17918730b463bb2c9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d8a67b4261fb520b7b64c3c039dcabd5f47f1ae6ca377d1fa4d0a6d0dd9ac2574a83cc0024923273a754527c9e0cba96c75e0923918d1fd997ba62f8f1dd6ae7
|
7
|
+
data.tar.gz: 79218840d95ce17d7a62d40e2f4cfca09498973b784677175ec1cbf67a9ed801074fad18a716dba2a16f89c7499a030b53e3c9c5fbf1b5f412b7cbd0530cd628
|
data/.parallel_rspec
ADDED
data/.rspec
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,24 @@ 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.3] - 2025-09-16
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Smart branch defaults: worktrees now use session name as default branch
|
12
|
+
- Remote branch tracking with `remote:` prefix syntax (e.g., `sxn worktree add project remote:origin/feature`)
|
13
|
+
- Automatic orphaned worktree recovery and cleanup
|
14
|
+
- Enhanced error messages with actionable suggestions
|
15
|
+
|
16
|
+
### Changed
|
17
|
+
- Improved worktree creation logic to handle existing/orphaned states
|
18
|
+
- Better error handling for remote branch operations
|
19
|
+
- Updated CLI documentation with new branch options
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Orphaned worktree cleanup now works for both existing and missing directories
|
23
|
+
- Worktree creation properly handles branch conflicts
|
24
|
+
- Test suite compatibility with new worktree features
|
25
|
+
|
8
26
|
## [0.2.1] - 2025-01-20
|
9
27
|
|
10
28
|
### Fixed
|
@@ -53,5 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
53
71
|
- Initial placeholder release
|
54
72
|
- Basic gem structure
|
55
73
|
|
74
|
+
[0.2.3]: https://github.com/idl3/sxn/compare/v0.2.1...v0.2.3
|
75
|
+
[0.2.1]: https://github.com/idl3/sxn/compare/v0.2.0...v0.2.1
|
56
76
|
[0.2.0]: https://github.com/idl3/sxn/compare/v0.1.0...v0.2.0
|
57
77
|
[0.1.0]: https://github.com/idl3/sxn/releases/tag/v0.1.0
|
data/Gemfile
CHANGED
@@ -10,8 +10,9 @@ gem "rake", "~> 13.0"
|
|
10
10
|
|
11
11
|
gem "rubocop", "~> 1.21"
|
12
12
|
|
13
|
-
# Test coverage
|
13
|
+
# Test coverage and parallel testing
|
14
14
|
group :test do
|
15
|
+
gem "parallel_tests", "~> 4.0"
|
15
16
|
gem "simplecov", "~> 0.22", require: false
|
16
17
|
gem "simplecov-console", require: false
|
17
18
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
sxn (0.2.
|
4
|
+
sxn (0.2.3)
|
5
5
|
async (~> 2.0)
|
6
6
|
bcrypt (~> 3.1)
|
7
7
|
dry-configurable (~> 1.0)
|
@@ -146,6 +146,8 @@ GEM
|
|
146
146
|
openssl (3.3.0)
|
147
147
|
ostruct (0.6.3)
|
148
148
|
parallel (1.27.0)
|
149
|
+
parallel_tests (4.10.1)
|
150
|
+
parallel
|
149
151
|
parser (3.3.9.0)
|
150
152
|
ast (~> 2.4.1)
|
151
153
|
racc
|
@@ -311,6 +313,7 @@ DEPENDENCIES
|
|
311
313
|
faker (~> 3.2)
|
312
314
|
irb
|
313
315
|
memory_profiler (~> 1.0)
|
316
|
+
parallel_tests (~> 4.0)
|
314
317
|
rake (~> 13.0)
|
315
318
|
rbs (~> 3.4)
|
316
319
|
rbs_rails (~> 0.12)
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
#
|
1
|
+
# sxn
|
2
2
|
|
3
3
|
[](https://github.com/idl3/sxn/actions/workflows/ci.yml)
|
4
4
|
[](https://www.ruby-lang.org)
|
5
5
|
[](LICENSE.txt)
|
6
6
|
|
7
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
181
|
+
### Setup
|
182
182
|
|
183
|
-
|
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
|
261
|
+
ENABLE_SIMPLECOV=true bundle exec parallel_rspec spec/
|
194
262
|
```
|
195
263
|
|
196
264
|
### Type Checking
|
@@ -223,3 +291,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/yourus
|
|
223
291
|
## License
|
224
292
|
|
225
293
|
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
294
|
+
|
295
|
+
## Author's Note
|
296
|
+
|
297
|
+
This is a personal project that leverages Claude Code as the primary and active developer. As we continue to refine the development process and iron out any kinks, you can expect builds to gradually become more stable. Your patience and feedback are greatly appreciated as we evolve this tool together.
|
data/Rakefile
CHANGED
@@ -13,6 +13,47 @@ rescue LoadError
|
|
13
13
|
# RSpec not available
|
14
14
|
end
|
15
15
|
|
16
|
+
# Parallel testing tasks
|
17
|
+
begin
|
18
|
+
require "parallel_tests/tasks"
|
19
|
+
rescue LoadError
|
20
|
+
# parallel_tests not available
|
21
|
+
end
|
22
|
+
|
23
|
+
namespace :parallel do
|
24
|
+
desc "Run specs in parallel"
|
25
|
+
task :spec do
|
26
|
+
sh "bundle exec parallel_rspec spec/unit spec/integration spec/performance --runtime-log tmp/parallel_runtime_rspec.log"
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Run specs in parallel with coverage"
|
30
|
+
task :spec_with_coverage do
|
31
|
+
ENV["ENABLE_SIMPLECOV"] = "true"
|
32
|
+
sh "bundle exec parallel_rspec spec/unit spec/integration spec/performance --runtime-log tmp/parallel_runtime_rspec.log"
|
33
|
+
end
|
34
|
+
|
35
|
+
desc "Setup parallel test databases (if needed)"
|
36
|
+
task :setup do
|
37
|
+
puts "No database setup needed for this project"
|
38
|
+
end
|
39
|
+
|
40
|
+
desc "Run specs in parallel with custom processor count"
|
41
|
+
task :spec_custom, [:processors] do |_t, args|
|
42
|
+
processors = args[:processors] || 4
|
43
|
+
sh "bundle exec parallel_rspec -n #{processors} spec/unit spec/integration spec/performance --runtime-log tmp/parallel_runtime_rspec.log"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Generate parallel test runtime log"
|
47
|
+
task :generate_runtime do
|
48
|
+
puts "Generating runtime log for balanced test distribution..."
|
49
|
+
ENV["TEST_ENV_NUMBER"] = "1"
|
50
|
+
specs = "spec/unit spec/integration spec/performance"
|
51
|
+
sh "bundle exec rspec --format progress --format ParallelTests::RSpec::RuntimeLogger " \
|
52
|
+
"--out tmp/parallel_runtime_rspec.log #{specs}"
|
53
|
+
puts "Runtime log generated at tmp/parallel_runtime_rspec.log"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
16
57
|
# Type checking tasks
|
17
58
|
namespace :rbs do
|
18
59
|
desc "Validate RBS files syntax"
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Parallel Testing Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
The sxn project uses `parallel_tests` gem to run RSpec tests in parallel, significantly reducing test execution time from ~35 seconds to ~11 seconds.
|
6
|
+
|
7
|
+
## Configuration
|
8
|
+
|
9
|
+
### `.parallel_rspec` File
|
10
|
+
|
11
|
+
This file configures RSpec formatters for parallel execution:
|
12
|
+
|
13
|
+
```
|
14
|
+
--format progress
|
15
|
+
--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
|
16
|
+
```
|
17
|
+
|
18
|
+
The RuntimeLogger records execution times for each test file, enabling balanced test distribution across parallel processes.
|
19
|
+
|
20
|
+
### Runtime-Based Test Distribution
|
21
|
+
|
22
|
+
Tests are distributed across parallel processes based on their historical execution times, ensuring all processes finish at roughly the same time.
|
23
|
+
|
24
|
+
## Local Usage
|
25
|
+
|
26
|
+
### Run Tests in Parallel
|
27
|
+
|
28
|
+
```bash
|
29
|
+
# Default parallel execution (uses all available CPUs)
|
30
|
+
bundle exec rake parallel:spec
|
31
|
+
|
32
|
+
# With custom processor count
|
33
|
+
bundle exec rake parallel:spec_custom[2] # Use 2 processes
|
34
|
+
|
35
|
+
# With coverage
|
36
|
+
bundle exec rake parallel:spec_with_coverage
|
37
|
+
```
|
38
|
+
|
39
|
+
### Generate/Update Runtime Log
|
40
|
+
|
41
|
+
```bash
|
42
|
+
# Generate initial runtime log or update existing one
|
43
|
+
bundle exec rake parallel:generate_runtime
|
44
|
+
```
|
45
|
+
|
46
|
+
The runtime log is stored at `tmp/parallel_runtime_rspec.log` and contains execution times for each spec file.
|
47
|
+
|
48
|
+
### Direct parallel_rspec Command
|
49
|
+
|
50
|
+
```bash
|
51
|
+
# Run with runtime balancing
|
52
|
+
bundle exec parallel_rspec spec/unit spec/integration spec/performance --runtime-log tmp/parallel_runtime_rspec.log
|
53
|
+
|
54
|
+
# Verbose output to see how tests are distributed
|
55
|
+
bundle exec parallel_rspec spec --verbose-command --runtime-log tmp/parallel_runtime_rspec.log
|
56
|
+
```
|
57
|
+
|
58
|
+
## CI/CD Integration
|
59
|
+
|
60
|
+
GitHub Actions is configured to:
|
61
|
+
|
62
|
+
1. **Cache Runtime Log**: The runtime log is cached between runs using the spec files' hash as the cache key
|
63
|
+
2. **Parallel Matrix Jobs**: Tests run across 4 parallel jobs
|
64
|
+
3. **Automatic Generation**: If no cached runtime log exists, CI generates one automatically
|
65
|
+
4. **Result Aggregation**: Test results from all parallel jobs are collected and summarized
|
66
|
+
|
67
|
+
### CI Configuration
|
68
|
+
|
69
|
+
The workflow uses a matrix strategy to run tests in parallel:
|
70
|
+
|
71
|
+
```yaml
|
72
|
+
matrix:
|
73
|
+
ruby: ['3.4.5']
|
74
|
+
ci_node_total: [4]
|
75
|
+
ci_node_index: [0, 1, 2, 3]
|
76
|
+
```
|
77
|
+
|
78
|
+
Each job runs a subset of tests based on the runtime log distribution.
|
79
|
+
|
80
|
+
## Benefits
|
81
|
+
|
82
|
+
- **Speed**: ~3x faster test execution (35s → 11s)
|
83
|
+
- **Balanced Distribution**: Tests are distributed based on execution time, not file count
|
84
|
+
- **CI Optimization**: Parallel jobs in CI reduce overall build time
|
85
|
+
- **Coverage Support**: SimpleCov properly merges results from parallel processes
|
86
|
+
|
87
|
+
## Troubleshooting
|
88
|
+
|
89
|
+
### Unbalanced Test Distribution
|
90
|
+
|
91
|
+
If tests aren't evenly distributed:
|
92
|
+
|
93
|
+
1. Regenerate the runtime log: `bundle exec rake parallel:generate_runtime`
|
94
|
+
2. Check the log file exists: `ls -la tmp/parallel_runtime_rspec.log`
|
95
|
+
3. Verify the format (should be `path/to/spec.rb:execution_time`)
|
96
|
+
|
97
|
+
### Missing Runtime Log
|
98
|
+
|
99
|
+
The system will fall back to file-size-based distribution if no runtime log is found. To ensure runtime-based distribution:
|
100
|
+
|
101
|
+
```bash
|
102
|
+
# Generate if missing
|
103
|
+
[ -f tmp/parallel_runtime_rspec.log ] || bundle exec rake parallel:generate_runtime
|
104
|
+
```
|
105
|
+
|
106
|
+
### Coverage Issues
|
107
|
+
|
108
|
+
Ensure SimpleCov is configured for parallel tests in `spec/spec_helper.rb`:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
if ENV["TEST_ENV_NUMBER"]
|
112
|
+
SimpleCov.command_name "RSpec_#{ENV['TEST_ENV_NUMBER']}"
|
113
|
+
SimpleCov.merge_timeout 3600
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
## Performance Metrics
|
118
|
+
|
119
|
+
Current performance with 4 parallel processes:
|
120
|
+
- Sequential execution: ~35 seconds
|
121
|
+
- Parallel execution: ~11 seconds
|
122
|
+
- Speedup: ~3.2x
|
123
|
+
|
124
|
+
The runtime-based distribution ensures all processes finish within 1-2 seconds of each other, maximizing efficiency.
|
data/lib/sxn/CLI.rb
CHANGED
@@ -32,16 +32,8 @@ module Sxn
|
|
32
32
|
# steep:ignore:start - Thor dynamic argument validation handled at runtime
|
33
33
|
# Thor framework uses metaprogramming for argument parsing that can't be statically typed.
|
34
34
|
# Runtime validation ensures type safety through Thor's built-in validation.
|
35
|
-
# Validate arguments, filtering out nil values for optional arguments
|
36
|
-
args_for_validation = [folder].compact
|
37
|
-
expected_arg_count = folder.nil? ? 0 : 1
|
38
|
-
|
39
|
-
RuntimeValidations.validate_thor_arguments("init", args_for_validation, options, {
|
40
|
-
args: { count: [expected_arg_count], types: [String] },
|
41
|
-
options: { force: :boolean, auto_detect: :boolean, quiet: :boolean }
|
42
|
-
})
|
43
|
-
|
44
35
|
Commands::Init.new.init(folder)
|
36
|
+
# steep:ignore:end
|
45
37
|
rescue Sxn::Error => e
|
46
38
|
handle_error(e)
|
47
39
|
end
|
@@ -78,6 +70,30 @@ module Sxn
|
|
78
70
|
handle_error(e)
|
79
71
|
end
|
80
72
|
|
73
|
+
desc "remove [SESSION_NAME]", "Remove a session (shortcut for 'sxn sessions remove')"
|
74
|
+
option :force, type: :boolean, aliases: "-f", desc: "Force removal even with uncommitted changes"
|
75
|
+
def remove(session_name = nil)
|
76
|
+
cmd = Commands::Sessions.new
|
77
|
+
cmd.options = options
|
78
|
+
cmd.remove(session_name)
|
79
|
+
rescue Sxn::Error => e
|
80
|
+
handle_error(e)
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "archive [SESSION_NAME]", "Archive a session (shortcut for 'sxn sessions archive')"
|
84
|
+
def archive(session_name = nil)
|
85
|
+
Commands::Sessions.new.archive(session_name)
|
86
|
+
rescue Sxn::Error => e
|
87
|
+
handle_error(e)
|
88
|
+
end
|
89
|
+
|
90
|
+
desc "activate [SESSION_NAME]", "Activate an archived session (shortcut for 'sxn sessions activate')"
|
91
|
+
def activate(session_name = nil)
|
92
|
+
Commands::Sessions.new.activate(session_name)
|
93
|
+
rescue Sxn::Error => e
|
94
|
+
handle_error(e)
|
95
|
+
end
|
96
|
+
|
81
97
|
desc "projects SUBCOMMAND", "Manage project configurations"
|
82
98
|
def projects(subcommand = nil, *args)
|
83
99
|
Commands::Projects.start([subcommand, *args].compact)
|
@@ -200,18 +216,14 @@ module Sxn
|
|
200
216
|
|
201
217
|
# steep:ignore:start - Safe integer to string coercion for UI display
|
202
218
|
# These integer values are safely converted to strings for display purposes.
|
203
|
-
|
204
|
-
@ui.key_value("Total
|
205
|
-
RuntimeValidations.validate_and_coerce_type(sessions.size, String, "session count display"))
|
206
|
-
@ui.key_value("Total Projects",
|
207
|
-
RuntimeValidations.validate_and_coerce_type(projects.size, String, "project count display"))
|
219
|
+
@ui.key_value("Total Sessions", sessions.size.to_s)
|
220
|
+
@ui.key_value("Total Projects", projects.size.to_s)
|
208
221
|
|
209
222
|
# Active worktrees
|
210
223
|
if current_session
|
211
224
|
worktree_manager = Sxn::Core::WorktreeManager.new(config_manager, session_manager)
|
212
225
|
worktrees = worktree_manager.list_worktrees(session_name: current_session)
|
213
|
-
@ui.key_value("Active Worktrees",
|
214
|
-
RuntimeValidations.validate_and_coerce_type(worktrees.size, String, "worktree count display"))
|
226
|
+
@ui.key_value("Active Worktrees", worktrees.size.to_s)
|
215
227
|
end
|
216
228
|
|
217
229
|
@ui.newline
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "thor"
|
4
|
+
require "time"
|
4
5
|
|
5
6
|
module Sxn
|
6
7
|
module Commands
|
@@ -79,7 +80,8 @@ module Sxn
|
|
79
80
|
name = @prompt.select("Select session to remove:", choices)
|
80
81
|
end
|
81
82
|
|
82
|
-
|
83
|
+
# Skip confirmation if force flag is used
|
84
|
+
if !options[:force] && !@prompt.confirm_deletion(name, "session")
|
83
85
|
@ui.info("Cancelled")
|
84
86
|
return
|
85
87
|
end
|
@@ -259,8 +261,8 @@ module Sxn
|
|
259
261
|
|
260
262
|
@ui.key_value("Linear Task", session[:linear_task]) if session[:linear_task]
|
261
263
|
|
262
|
-
@ui.key_value("Created", session[:created_at]
|
263
|
-
@ui.key_value("Updated", session[:updated_at]
|
264
|
+
@ui.key_value("Created", format_timestamp(session[:created_at]))
|
265
|
+
@ui.key_value("Updated", format_timestamp(session[:updated_at]))
|
264
266
|
|
265
267
|
if verbose && session[:projects]&.any?
|
266
268
|
@ui.newline
|
@@ -295,6 +297,19 @@ module Sxn
|
|
295
297
|
@ui.newline
|
296
298
|
@ui.recovery_suggestion("Create your first session with 'sxn add <session-name>'")
|
297
299
|
end
|
300
|
+
|
301
|
+
def format_timestamp(timestamp)
|
302
|
+
return "Unknown" if timestamp.nil? || timestamp.empty?
|
303
|
+
|
304
|
+
# Parse the ISO8601 timestamp and convert to local time
|
305
|
+
time = Time.parse(timestamp)
|
306
|
+
local_time = time.localtime
|
307
|
+
|
308
|
+
# Format as "YYYY-MM-DD HH:MM:SS AM/PM Timezone"
|
309
|
+
local_time.strftime("%Y-%m-%d %I:%M:%S %p %Z")
|
310
|
+
rescue ArgumentError
|
311
|
+
timestamp # Return original if parsing fails
|
312
|
+
end
|
298
313
|
end
|
299
314
|
end
|
300
315
|
end
|
@@ -19,7 +19,25 @@ module Sxn
|
|
19
19
|
@worktree_manager = Sxn::Core::WorktreeManager.new(@config_manager, @session_manager)
|
20
20
|
end
|
21
21
|
|
22
|
-
desc "add PROJECT [BRANCH]", "Add worktree to current session"
|
22
|
+
desc "add PROJECT [BRANCH]", "Add worktree to current session (defaults branch to session name)"
|
23
|
+
long_desc <<-DESC
|
24
|
+
Add a worktree for a project to the current session.
|
25
|
+
|
26
|
+
Branch options:
|
27
|
+
- No branch specified: Uses the session name as the branch name
|
28
|
+
- Branch name: Creates or checks out the specified branch
|
29
|
+
- remote:<branch>: Fetches and tracks the remote branch
|
30
|
+
|
31
|
+
Examples:
|
32
|
+
- sxn worktree add atlas-core
|
33
|
+
Creates worktree with branch name matching current session
|
34
|
+
|
35
|
+
- sxn worktree add atlas-core feature-branch
|
36
|
+
Creates worktree with specified branch name
|
37
|
+
|
38
|
+
- sxn worktree add atlas-core remote:origin/main
|
39
|
+
Fetches and tracks the remote branch
|
40
|
+
DESC
|
23
41
|
option :session, type: :string, aliases: "-s", desc: "Target session (defaults to current)"
|
24
42
|
option :apply_rules, type: :boolean, default: true, desc: "Apply project rules after creation"
|
25
43
|
option :interactive, type: :boolean, aliases: "-i", desc: "Interactive mode"
|
@@ -33,10 +51,11 @@ module Sxn
|
|
33
51
|
return if project_name.nil?
|
34
52
|
end
|
35
53
|
|
36
|
-
# Interactive branch selection if not provided
|
54
|
+
# Interactive branch selection if not provided and interactive mode
|
55
|
+
# Note: If branch is nil, WorktreeManager will use session name as default
|
37
56
|
if options[:interactive] && branch.nil?
|
38
|
-
|
39
|
-
branch = @prompt.branch_name("Enter branch name:", default:
|
57
|
+
session_name = options[:session] || @config_manager.current_session
|
58
|
+
branch = @prompt.branch_name("Enter branch name:", default: session_name)
|
40
59
|
end
|
41
60
|
|
42
61
|
session_name = options[:session] || @config_manager.current_session
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require "fileutils"
|
4
4
|
require "yaml"
|
5
5
|
require "pathname"
|
6
|
+
require "ostruct"
|
6
7
|
|
7
8
|
module Sxn
|
8
9
|
module Core
|
@@ -40,7 +41,10 @@ module Sxn
|
|
40
41
|
raise Sxn::ConfigurationError, "Project not initialized. Run 'sxn init' first." unless initialized?
|
41
42
|
|
42
43
|
discovery = Sxn::Config::ConfigDiscovery.new(@base_path)
|
43
|
-
discovery.discover_config
|
44
|
+
config_hash = discovery.discover_config
|
45
|
+
|
46
|
+
# Convert nested hashes to OpenStruct recursively
|
47
|
+
config_to_struct(config_hash)
|
44
48
|
end
|
45
49
|
|
46
50
|
def update_current_session(session_name)
|
@@ -194,6 +198,14 @@ module Sxn
|
|
194
198
|
|
195
199
|
private
|
196
200
|
|
201
|
+
def config_to_struct(obj)
|
202
|
+
return obj unless obj.is_a?(Hash)
|
203
|
+
|
204
|
+
OpenStruct.new(
|
205
|
+
obj.transform_values { |v| config_to_struct(v) }
|
206
|
+
)
|
207
|
+
end
|
208
|
+
|
197
209
|
def sessions_folder_relative_path
|
198
210
|
return ".sxn" unless @sessions_folder
|
199
211
|
|
@@ -25,8 +25,30 @@ module Sxn
|
|
25
25
|
project = @project_manager.get_project(project_name)
|
26
26
|
raise Sxn::ProjectNotFoundError, "Project '#{project_name}' not found" unless project
|
27
27
|
|
28
|
-
#
|
29
|
-
branch
|
28
|
+
# Determine branch name
|
29
|
+
# If no branch specified, use session name as the branch name
|
30
|
+
# If branch starts with "remote:", handle remote branch tracking
|
31
|
+
if branch.nil?
|
32
|
+
branch = session_name
|
33
|
+
elsif branch.start_with?("remote:")
|
34
|
+
remote_branch = branch.sub("remote:", "")
|
35
|
+
# Fetch the remote branch first
|
36
|
+
begin
|
37
|
+
fetch_remote_branch(project[:path], remote_branch)
|
38
|
+
branch = remote_branch
|
39
|
+
rescue StandardError => e
|
40
|
+
raise Sxn::WorktreeCreationError, "Failed to fetch remote branch: #{e.message}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if ENV["SXN_DEBUG"]
|
45
|
+
puts "[DEBUG] Adding worktree:"
|
46
|
+
puts " Project: #{project_name}"
|
47
|
+
puts " Project path: #{project[:path]}"
|
48
|
+
puts " Session: #{session_name}"
|
49
|
+
puts " Session path: #{session[:path]}"
|
50
|
+
puts " Branch: #{branch}"
|
51
|
+
end
|
30
52
|
|
31
53
|
# Check if worktree already exists in this session
|
32
54
|
existing_worktrees = @session_manager.get_session_worktrees(session_name)
|
@@ -38,7 +60,12 @@ module Sxn
|
|
38
60
|
# Create worktree path
|
39
61
|
worktree_path = File.join(session[:path], project_name)
|
40
62
|
|
63
|
+
puts " Worktree path: #{worktree_path}" if ENV["SXN_DEBUG"]
|
64
|
+
|
41
65
|
begin
|
66
|
+
# Handle orphaned worktree if it exists
|
67
|
+
handle_orphaned_worktree(project[:path], worktree_path)
|
68
|
+
|
42
69
|
# Create the worktree
|
43
70
|
create_git_worktree(project[:path], worktree_path, branch)
|
44
71
|
|
@@ -169,6 +196,47 @@ module Sxn
|
|
169
196
|
|
170
197
|
private
|
171
198
|
|
199
|
+
def handle_orphaned_worktree(project_path, worktree_path)
|
200
|
+
Dir.chdir(project_path) do
|
201
|
+
# Try to prune worktrees first
|
202
|
+
system("git worktree prune", out: File::NULL, err: File::NULL)
|
203
|
+
|
204
|
+
# Check if this worktree is registered (whether it exists or not)
|
205
|
+
output = `git worktree list --porcelain 2>/dev/null`
|
206
|
+
if output.include?(worktree_path)
|
207
|
+
# Force remove the orphaned/existing worktree
|
208
|
+
system("git worktree remove --force #{Shellwords.escape(worktree_path)}", out: File::NULL, err: File::NULL)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# Remove the directory if it still exists
|
213
|
+
FileUtils.rm_rf(worktree_path)
|
214
|
+
end
|
215
|
+
|
216
|
+
def fetch_remote_branch(project_path, branch_name)
|
217
|
+
Dir.chdir(project_path) do
|
218
|
+
# First, fetch all remotes to ensure we have the latest branches
|
219
|
+
raise "Failed to fetch remote branches" unless system("git fetch --all", out: File::NULL, err: File::NULL)
|
220
|
+
|
221
|
+
# Check if the branch exists on any remote
|
222
|
+
remotes = `git remote`.lines.map(&:strip)
|
223
|
+
branch_found = false
|
224
|
+
|
225
|
+
remotes.each do |remote|
|
226
|
+
next unless system("git show-ref --verify --quiet refs/remotes/#{remote}/#{Shellwords.escape(branch_name)}",
|
227
|
+
out: File::NULL, err: File::NULL)
|
228
|
+
|
229
|
+
branch_found = true
|
230
|
+
# Set up tracking for the remote branch
|
231
|
+
system("git branch --track #{Shellwords.escape(branch_name)} #{remote}/#{Shellwords.escape(branch_name)}",
|
232
|
+
out: File::NULL, err: File::NULL)
|
233
|
+
break
|
234
|
+
end
|
235
|
+
|
236
|
+
raise "Remote branch '#{branch_name}' not found on any remote" unless branch_found
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
172
240
|
def create_git_worktree(project_path, worktree_path, branch)
|
173
241
|
Dir.chdir(project_path) do
|
174
242
|
# Check if branch exists
|
@@ -183,8 +251,39 @@ module Sxn
|
|
183
251
|
["git", "worktree", "add", "-b", branch, worktree_path]
|
184
252
|
end
|
185
253
|
|
186
|
-
|
187
|
-
|
254
|
+
# Capture stderr for better error messages
|
255
|
+
require "open3"
|
256
|
+
stdout, stderr, status = Open3.capture3(*cmd)
|
257
|
+
|
258
|
+
unless status.success?
|
259
|
+
error_msg = stderr.empty? ? stdout : stderr
|
260
|
+
error_msg = "Git worktree command failed" if error_msg.strip.empty?
|
261
|
+
|
262
|
+
# Add more context to common errors
|
263
|
+
if error_msg.include?("already exists")
|
264
|
+
error_msg += "\nTry removing the existing worktree first with: sxn worktree remove #{File.basename(worktree_path)}"
|
265
|
+
elsif error_msg.include?("is already checked out")
|
266
|
+
error_msg += "\nThis branch is already checked out in another worktree"
|
267
|
+
elsif error_msg.include?("not a git repository")
|
268
|
+
error_msg = "Project '#{File.basename(project_path)}' is not a git repository"
|
269
|
+
elsif error_msg.include?("fatal: invalid reference")
|
270
|
+
# This typically means the branch doesn't exist and we're trying to create from a non-existent base
|
271
|
+
error_msg += "\nMake sure the repository has at least one commit or specify an existing branch"
|
272
|
+
elsif error_msg.include?("fatal:")
|
273
|
+
# Extract just the fatal error message for cleaner output
|
274
|
+
error_msg = error_msg.lines.grep(/fatal:/).first&.strip || error_msg
|
275
|
+
end
|
276
|
+
|
277
|
+
if ENV["SXN_DEBUG"]
|
278
|
+
puts "[DEBUG] Git worktree command failed:"
|
279
|
+
puts " Command: #{cmd.join(" ")}"
|
280
|
+
puts " Directory: #{project_path}"
|
281
|
+
puts " STDOUT: #{stdout}"
|
282
|
+
puts " STDERR: #{stderr}"
|
283
|
+
end
|
284
|
+
|
285
|
+
raise error_msg
|
286
|
+
end
|
188
287
|
end
|
189
288
|
end
|
190
289
|
|
@@ -25,7 +25,7 @@ module Sxn
|
|
25
25
|
# - Bulk operations: < 100ms for 100 sessions
|
26
26
|
class SessionDatabase
|
27
27
|
# Current database schema version for migrations
|
28
|
-
SCHEMA_VERSION =
|
28
|
+
SCHEMA_VERSION = 2
|
29
29
|
|
30
30
|
# Default database path relative to sxn config directory
|
31
31
|
DEFAULT_DB_PATH = ".sxn/sessions.db"
|
@@ -122,7 +122,7 @@ module Sxn
|
|
122
122
|
filters ||= {}
|
123
123
|
limit = limit.to_i if limit
|
124
124
|
offset = offset.to_i if offset
|
125
|
-
|
125
|
+
|
126
126
|
query_parts = ["SELECT * FROM sessions"]
|
127
127
|
params = []
|
128
128
|
|
@@ -420,6 +420,19 @@ module Sxn
|
|
420
420
|
def setup_database
|
421
421
|
current_version = get_schema_version
|
422
422
|
|
423
|
+
# Check if this is an old database without version info
|
424
|
+
# If sessions table exists but version is 0, it's likely an old database
|
425
|
+
if current_version.zero? && table_exists?("sessions")
|
426
|
+
# Check if it's the old schema (without worktrees/projects columns)
|
427
|
+
columns = connection.execute("PRAGMA table_info(sessions)").map { |col| col["name"] }
|
428
|
+
if !columns.include?("worktrees") || !columns.include?("projects")
|
429
|
+
# This is an old database, set it to version 1 so we can migrate it
|
430
|
+
Sxn.logger.info "Detected old database schema without version info, treating as version 1"
|
431
|
+
set_schema_version(1)
|
432
|
+
current_version = 1
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
423
436
|
if current_version.zero?
|
424
437
|
create_initial_schema
|
425
438
|
set_schema_version(SCHEMA_VERSION)
|
@@ -428,6 +441,14 @@ module Sxn
|
|
428
441
|
end
|
429
442
|
end
|
430
443
|
|
444
|
+
# Check if a table exists
|
445
|
+
def table_exists?(table_name)
|
446
|
+
result = connection.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", table_name)
|
447
|
+
!result.empty?
|
448
|
+
rescue SQLite3::Exception
|
449
|
+
false
|
450
|
+
end
|
451
|
+
|
431
452
|
# Get current schema version from database
|
432
453
|
def get_schema_version
|
433
454
|
result = connection.execute("PRAGMA user_version").first
|
@@ -501,12 +522,41 @@ module Sxn
|
|
501
522
|
end
|
502
523
|
|
503
524
|
# Run database migrations from old version to current
|
504
|
-
def run_migrations(
|
505
|
-
|
506
|
-
|
525
|
+
def run_migrations(from_version)
|
526
|
+
Sxn.logger.info "Running database migrations from version #{from_version} to #{SCHEMA_VERSION}"
|
527
|
+
|
528
|
+
# Run each migration in sequence
|
529
|
+
migrate_to_v1 if from_version < 1
|
530
|
+
|
531
|
+
migrate_to_v2 if from_version < 2
|
532
|
+
|
507
533
|
set_schema_version(SCHEMA_VERSION)
|
508
534
|
end
|
509
535
|
|
536
|
+
# Migration: Initial schema (v1)
|
537
|
+
def migrate_to_v1
|
538
|
+
# This is handled by create_initial_schema
|
539
|
+
create_initial_schema
|
540
|
+
end
|
541
|
+
|
542
|
+
# Migration: Add worktrees and projects columns (v2)
|
543
|
+
def migrate_to_v2
|
544
|
+
Sxn.logger.info "Migrating database to version 2: Adding worktrees and projects columns"
|
545
|
+
|
546
|
+
# Check if columns already exist
|
547
|
+
columns = connection.execute("PRAGMA table_info(sessions)").map { |col| col["name"] }
|
548
|
+
|
549
|
+
unless columns.include?("worktrees")
|
550
|
+
connection.execute("ALTER TABLE sessions ADD COLUMN worktrees TEXT")
|
551
|
+
Sxn.logger.info "Added worktrees column to sessions table"
|
552
|
+
end
|
553
|
+
|
554
|
+
return if columns.include?("projects")
|
555
|
+
|
556
|
+
connection.execute("ALTER TABLE sessions ADD COLUMN projects TEXT")
|
557
|
+
Sxn.logger.info "Added projects column to sessions table"
|
558
|
+
end
|
559
|
+
|
510
560
|
# Generate secure, unique session ID
|
511
561
|
def generate_session_id
|
512
562
|
SecureRandom.hex(16) # 32 character hex string
|
@@ -636,7 +686,7 @@ module Sxn
|
|
636
686
|
param.to_s
|
637
687
|
end
|
638
688
|
end
|
639
|
-
|
689
|
+
|
640
690
|
connection.execute(sql, sanitized_params)
|
641
691
|
rescue SQLite3::MismatchException => e
|
642
692
|
# Log the error with details for debugging
|
@@ -6,6 +6,10 @@ module Sxn
|
|
6
6
|
class << self
|
7
7
|
# Validate Thor command arguments at runtime
|
8
8
|
def validate_thor_arguments(command_name, args, options, validations)
|
9
|
+
# Ensure args and options are not nil
|
10
|
+
args ||= []
|
11
|
+
options ||= {}
|
12
|
+
|
9
13
|
# Validate argument count
|
10
14
|
if validations[:args]
|
11
15
|
count_range = validations[:args][:count]
|
@@ -23,7 +27,7 @@ module Sxn
|
|
23
27
|
end
|
24
28
|
|
25
29
|
# Validate options
|
26
|
-
if validations[:options]
|
30
|
+
if validations[:options] && options.respond_to?(:each)
|
27
31
|
options.each do |key, value|
|
28
32
|
validate_option_type(command_name, key, value, validations[:options][key.to_sym]) if validations[:options][key.to_sym]
|
29
33
|
end
|
data/lib/sxn/version.rb
CHANGED
data/script/setup-hooks
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
#
|
3
|
+
# Setup git hooks for sxn development
|
4
|
+
#
|
5
|
+
|
6
|
+
set -e
|
7
|
+
|
8
|
+
echo "🔧 Setting up git hooks for sxn development..."
|
9
|
+
|
10
|
+
# Create hooks directory if it doesn't exist
|
11
|
+
mkdir -p .git/hooks
|
12
|
+
|
13
|
+
# Create pre-push hook
|
14
|
+
cat > .git/hooks/pre-push << 'EOF'
|
15
|
+
#!/bin/sh
|
16
|
+
#
|
17
|
+
# Pre-push hook for sxn project
|
18
|
+
# Runs RuboCop and RSpec before allowing push
|
19
|
+
#
|
20
|
+
|
21
|
+
echo "🔍 Running pre-push checks..."
|
22
|
+
|
23
|
+
# Run RuboCop
|
24
|
+
echo "📝 Checking code style with RuboCop..."
|
25
|
+
bundle exec rubocop
|
26
|
+
if [ $? -ne 0 ]; then
|
27
|
+
echo "❌ RuboCop failed! Please fix linting issues before pushing."
|
28
|
+
echo " Run: bundle exec rubocop -a"
|
29
|
+
exit 1
|
30
|
+
fi
|
31
|
+
|
32
|
+
# Run RSpec tests
|
33
|
+
echo "🧪 Running tests with RSpec..."
|
34
|
+
bundle exec rspec --format progress
|
35
|
+
if [ $? -ne 0 ]; then
|
36
|
+
echo "❌ Tests failed! Please fix failing tests before pushing."
|
37
|
+
exit 1
|
38
|
+
fi
|
39
|
+
|
40
|
+
echo "✅ All checks passed! Proceeding with push..."
|
41
|
+
exit 0
|
42
|
+
EOF
|
43
|
+
|
44
|
+
chmod +x .git/hooks/pre-push
|
45
|
+
|
46
|
+
echo "✅ Git hooks installed successfully!"
|
47
|
+
echo ""
|
48
|
+
echo "The following hooks have been set up:"
|
49
|
+
echo " • pre-push: Runs RuboCop and RSpec before pushing"
|
50
|
+
echo ""
|
51
|
+
echo "To bypass hooks in emergency (not recommended):"
|
52
|
+
echo " git push --no-verify"
|
data/sig/sxn/cli.rbs
CHANGED
@@ -21,6 +21,12 @@ module Sxn
|
|
21
21
|
|
22
22
|
def current: () -> void
|
23
23
|
|
24
|
+
def remove: (?String? session_name) -> void
|
25
|
+
|
26
|
+
def archive: (?String? session_name) -> void
|
27
|
+
|
28
|
+
def activate: (?String? session_name) -> void
|
29
|
+
|
24
30
|
def projects: (?String? subcommand, *String args) -> void
|
25
31
|
|
26
32
|
def sessions: (?String? subcommand, *String args) -> void
|
@@ -57,6 +57,10 @@ module Sxn
|
|
57
57
|
|
58
58
|
# Suggest creating a session
|
59
59
|
def suggest_create_session: () -> void
|
60
|
+
|
61
|
+
# Format a timestamp string to local timezone
|
62
|
+
# @param timestamp ISO8601 timestamp string
|
63
|
+
def format_timestamp: (String? timestamp) -> String
|
60
64
|
end
|
61
65
|
end
|
62
66
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sxn
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ernest Sim
|
@@ -488,6 +488,8 @@ files:
|
|
488
488
|
- ".gem_rbs_collection/sqlite3/2.0/.rbs_meta.yaml"
|
489
489
|
- ".gem_rbs_collection/sqlite3/2.0/database.rbs"
|
490
490
|
- ".gem_rbs_collection/sqlite3/2.0/pragmas.rbs"
|
491
|
+
- ".parallel_rspec"
|
492
|
+
- ".parallel_rspec_options"
|
491
493
|
- ".rspec"
|
492
494
|
- ".rubocop.yml"
|
493
495
|
- ".simplecov"
|
@@ -499,6 +501,7 @@ files:
|
|
499
501
|
- Rakefile
|
500
502
|
- Steepfile
|
501
503
|
- bin/sxn
|
504
|
+
- docs/parallel_testing.md
|
502
505
|
- lib/sxn.rb
|
503
506
|
- lib/sxn/CLI.rb
|
504
507
|
- lib/sxn/commands.rb
|
@@ -555,6 +558,7 @@ files:
|
|
555
558
|
- lib/sxn/version.rb
|
556
559
|
- rbs_collection.lock.yaml
|
557
560
|
- rbs_collection.yaml
|
561
|
+
- script/setup-hooks
|
558
562
|
- scripts/test.sh
|
559
563
|
- sig/external/liquid.rbs
|
560
564
|
- sig/external/thor.rbs
|
@@ -606,7 +610,6 @@ files:
|
|
606
610
|
- sig/sxn/ui/prompt.rbs
|
607
611
|
- sig/sxn/ui/table.rbs
|
608
612
|
- sig/sxn/version.rbs
|
609
|
-
- sxn.gemspec
|
610
613
|
homepage: https://github.com/idl3/sxn
|
611
614
|
licenses:
|
612
615
|
- MIT
|
data/sxn.gemspec
DELETED
@@ -1,85 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative "lib/sxn/version"
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = "sxn"
|
7
|
-
spec.version = Sxn::VERSION
|
8
|
-
spec.authors = ["Ernest Sim"]
|
9
|
-
spec.email = ["ernest.codes@gmail.com"]
|
10
|
-
|
11
|
-
spec.summary = "Session management for multi-repository development"
|
12
|
-
spec.description = "Sxn simplifies git worktree management with intelligent project rules and secure automation"
|
13
|
-
spec.homepage = "https://github.com/idl3/sxn"
|
14
|
-
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = ">= 3.2.0"
|
16
|
-
|
17
|
-
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
-
spec.metadata["homepage_uri"] = spec.homepage
|
19
|
-
spec.metadata["source_code_uri"] = spec.homepage
|
20
|
-
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
21
|
-
spec.metadata["rubygems_mfa_required"] = "true"
|
22
|
-
|
23
|
-
# Specify which files should be added to the gem when it is released.
|
24
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
-
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
-
(f == __FILE__) ||
|
28
|
-
f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}) ||
|
29
|
-
f.match(/\.db-(?:shm|wal)\z/) || # Exclude SQLite temp files
|
30
|
-
f.match(/\.gem\z/) # Exclude gem files
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
spec.bindir = "bin"
|
35
|
-
spec.executables = spec.files.grep(%r{\Abin/}) { |f| File.basename(f) }
|
36
|
-
spec.require_paths = ["lib"]
|
37
|
-
|
38
|
-
# Core CLI dependencies
|
39
|
-
spec.add_dependency "pastel", "~> 0.8" # Terminal colors
|
40
|
-
spec.add_dependency "thor", "~> 1.3" # CLI framework
|
41
|
-
spec.add_dependency "tty-progressbar", "~> 0.18" # Progress bars
|
42
|
-
spec.add_dependency "tty-prompt", "~> 0.23" # Interactive prompts
|
43
|
-
spec.add_dependency "tty-table", "~> 0.12" # Table formatting
|
44
|
-
|
45
|
-
# Configuration and data management
|
46
|
-
spec.add_dependency "dry-configurable", "~> 1.0" # Configuration management
|
47
|
-
spec.add_dependency "sqlite3", "~> 1.6" # Session database
|
48
|
-
spec.add_dependency "zeitwerk", "~> 2.6" # Code loading
|
49
|
-
|
50
|
-
# Template engine (secure, sandboxed)
|
51
|
-
spec.add_dependency "liquid", "~> 5.4" # Safe template processing
|
52
|
-
|
53
|
-
# MCP server dependencies
|
54
|
-
spec.add_dependency "async", "~> 2.0" # Async operations
|
55
|
-
spec.add_dependency "json-schema", "~> 4.0" # Schema validation
|
56
|
-
|
57
|
-
# Security and encryption
|
58
|
-
spec.add_dependency "bcrypt", "~> 3.1" # Password hashing
|
59
|
-
spec.add_dependency "openssl", ">= 3.0" # Encryption support
|
60
|
-
spec.add_dependency "ostruct" # OpenStruct for Ruby 3.5+ compatibility
|
61
|
-
|
62
|
-
# File system operations
|
63
|
-
spec.add_dependency "listen", "~> 3.8" # File watching for config cache
|
64
|
-
spec.add_dependency "parallel", "~> 1.23" # Parallel execution
|
65
|
-
|
66
|
-
# Development dependencies
|
67
|
-
spec.add_development_dependency "aruba", "~> 2.1" # CLI testing
|
68
|
-
spec.add_development_dependency "benchmark" # Benchmark for Ruby 3.5+ compatibility
|
69
|
-
spec.add_development_dependency "benchmark-ips", "~> 2.12" # Performance benchmarking
|
70
|
-
spec.add_development_dependency "bundler", "~> 2.4"
|
71
|
-
spec.add_development_dependency "climate_control", "~> 1.2" # Environment variable testing
|
72
|
-
spec.add_development_dependency "faker", "~> 3.2" # Test data generation
|
73
|
-
spec.add_development_dependency "memory_profiler", "~> 1.0" # Memory profiling
|
74
|
-
spec.add_development_dependency "rake", "~> 13.0"
|
75
|
-
spec.add_development_dependency "rspec", "~> 3.12"
|
76
|
-
spec.add_development_dependency "rubocop", "~> 1.50" # Code linting
|
77
|
-
spec.add_development_dependency "rubocop-performance", "~> 1.16"
|
78
|
-
spec.add_development_dependency "rubocop-rspec", "~> 2.19"
|
79
|
-
spec.add_development_dependency "simplecov", "~> 0.22" # Code coverage
|
80
|
-
spec.add_development_dependency "vcr", "~> 6.2" # HTTP interaction recording
|
81
|
-
spec.add_development_dependency "webmock", "~> 3.19" # HTTP mocking for MCP tests
|
82
|
-
|
83
|
-
# For more information and examples about making a new gem, check out our
|
84
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
85
|
-
end
|