sxn 0.2.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 +7 -0
- data/.gem_rbs_collection/addressable/2.8/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/addressable/2.8/addressable.rbs +62 -0
- data/.gem_rbs_collection/async/2.12/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/async/2.12/async.rbs +119 -0
- data/.gem_rbs_collection/async/2.12/kernel.rbs +5 -0
- data/.gem_rbs_collection/async/2.12/manifest.yaml +7 -0
- data/.gem_rbs_collection/bcrypt/3.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/bcrypt/3.1/bcrypt.rbs +47 -0
- data/.gem_rbs_collection/bcrypt/3.1/manifest.yaml +2 -0
- data/.gem_rbs_collection/bigdecimal/3.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal-math.rbs +119 -0
- data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal.rbs +1630 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +4 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +26 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +4 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +65 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +249 -0
- data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +5 -0
- data/.gem_rbs_collection/diff-lcs/1.5/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/diff-lcs/1.5/diff-lcs.rbs +11 -0
- data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/listen/3.9/listen.rbs +25 -0
- data/.gem_rbs_collection/listen/3.9/listener.rbs +24 -0
- data/.gem_rbs_collection/mini_mime/0.1/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/mini_mime/0.1/mini_mime.rbs +14 -0
- data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
- data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
- data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
- data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +822 -0
- data/.gem_rbs_collection/sqlite3/2.0/.rbs_meta.yaml +9 -0
- data/.gem_rbs_collection/sqlite3/2.0/database.rbs +20 -0
- data/.gem_rbs_collection/sqlite3/2.0/pragmas.rbs +5 -0
- data/.rspec +4 -0
- data/.rubocop.yml +121 -0
- data/.simplecov +51 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +24 -0
- data/Gemfile.lock +329 -0
- data/LICENSE.txt +21 -0
- data/README.md +225 -0
- data/Rakefile +54 -0
- data/Steepfile +50 -0
- data/bin/sxn +6 -0
- data/lib/sxn/CLI.rb +275 -0
- data/lib/sxn/commands/init.rb +137 -0
- data/lib/sxn/commands/projects.rb +350 -0
- data/lib/sxn/commands/rules.rb +435 -0
- data/lib/sxn/commands/sessions.rb +300 -0
- data/lib/sxn/commands/worktrees.rb +416 -0
- data/lib/sxn/commands.rb +13 -0
- data/lib/sxn/config/config_cache.rb +295 -0
- data/lib/sxn/config/config_discovery.rb +242 -0
- data/lib/sxn/config/config_validator.rb +562 -0
- data/lib/sxn/config.rb +259 -0
- data/lib/sxn/core/config_manager.rb +290 -0
- data/lib/sxn/core/project_manager.rb +307 -0
- data/lib/sxn/core/rules_manager.rb +306 -0
- data/lib/sxn/core/session_manager.rb +336 -0
- data/lib/sxn/core/worktree_manager.rb +281 -0
- data/lib/sxn/core.rb +13 -0
- data/lib/sxn/database/errors.rb +29 -0
- data/lib/sxn/database/session_database.rb +691 -0
- data/lib/sxn/database.rb +24 -0
- data/lib/sxn/errors.rb +76 -0
- data/lib/sxn/rules/base_rule.rb +367 -0
- data/lib/sxn/rules/copy_files_rule.rb +346 -0
- data/lib/sxn/rules/errors.rb +28 -0
- data/lib/sxn/rules/project_detector.rb +871 -0
- data/lib/sxn/rules/rules_engine.rb +485 -0
- data/lib/sxn/rules/setup_commands_rule.rb +307 -0
- data/lib/sxn/rules/template_rule.rb +262 -0
- data/lib/sxn/rules.rb +148 -0
- data/lib/sxn/runtime_validations.rb +96 -0
- data/lib/sxn/security/secure_command_executor.rb +364 -0
- data/lib/sxn/security/secure_file_copier.rb +478 -0
- data/lib/sxn/security/secure_path_validator.rb +258 -0
- data/lib/sxn/security.rb +15 -0
- data/lib/sxn/templates/common/gitignore.liquid +99 -0
- data/lib/sxn/templates/common/session-info.md.liquid +58 -0
- data/lib/sxn/templates/errors.rb +36 -0
- data/lib/sxn/templates/javascript/README.md.liquid +59 -0
- data/lib/sxn/templates/javascript/session-info.md.liquid +206 -0
- data/lib/sxn/templates/rails/CLAUDE.md.liquid +78 -0
- data/lib/sxn/templates/rails/database.yml.liquid +31 -0
- data/lib/sxn/templates/rails/session-info.md.liquid +144 -0
- data/lib/sxn/templates/template_engine.rb +346 -0
- data/lib/sxn/templates/template_processor.rb +279 -0
- data/lib/sxn/templates/template_security.rb +410 -0
- data/lib/sxn/templates/template_variables.rb +713 -0
- data/lib/sxn/templates.rb +28 -0
- data/lib/sxn/ui/output.rb +103 -0
- data/lib/sxn/ui/progress_bar.rb +91 -0
- data/lib/sxn/ui/prompt.rb +116 -0
- data/lib/sxn/ui/table.rb +183 -0
- data/lib/sxn/ui.rb +12 -0
- data/lib/sxn/version.rb +5 -0
- data/lib/sxn.rb +63 -0
- data/rbs_collection.lock.yaml +180 -0
- data/rbs_collection.yaml +39 -0
- data/scripts/test.sh +31 -0
- data/sig/external/liquid.rbs +116 -0
- data/sig/external/thor.rbs +99 -0
- data/sig/external/tty.rbs +71 -0
- data/sig/sxn/cli.rbs +46 -0
- data/sig/sxn/commands/init.rbs +38 -0
- data/sig/sxn/commands/projects.rbs +72 -0
- data/sig/sxn/commands/rules.rbs +95 -0
- data/sig/sxn/commands/sessions.rbs +62 -0
- data/sig/sxn/commands/worktrees.rbs +82 -0
- data/sig/sxn/commands.rbs +6 -0
- data/sig/sxn/config/config_cache.rbs +67 -0
- data/sig/sxn/config/config_discovery.rbs +64 -0
- data/sig/sxn/config/config_validator.rbs +64 -0
- data/sig/sxn/config.rbs +74 -0
- data/sig/sxn/core/config_manager.rbs +67 -0
- data/sig/sxn/core/project_manager.rbs +52 -0
- data/sig/sxn/core/rules_manager.rbs +54 -0
- data/sig/sxn/core/session_manager.rbs +59 -0
- data/sig/sxn/core/worktree_manager.rbs +50 -0
- data/sig/sxn/core.rbs +87 -0
- data/sig/sxn/database/errors.rbs +37 -0
- data/sig/sxn/database/session_database.rbs +151 -0
- data/sig/sxn/database.rbs +83 -0
- data/sig/sxn/errors.rbs +89 -0
- data/sig/sxn/rules/base_rule.rbs +137 -0
- data/sig/sxn/rules/copy_files_rule.rbs +65 -0
- data/sig/sxn/rules/errors.rbs +33 -0
- data/sig/sxn/rules/project_detector.rbs +115 -0
- data/sig/sxn/rules/rules_engine.rbs +118 -0
- data/sig/sxn/rules/setup_commands_rule.rbs +60 -0
- data/sig/sxn/rules/template_rule.rbs +44 -0
- data/sig/sxn/rules.rbs +287 -0
- data/sig/sxn/runtime_validations.rbs +16 -0
- data/sig/sxn/security/secure_command_executor.rbs +63 -0
- data/sig/sxn/security/secure_file_copier.rbs +79 -0
- data/sig/sxn/security/secure_path_validator.rbs +30 -0
- data/sig/sxn/security.rbs +128 -0
- data/sig/sxn/templates/errors.rbs +43 -0
- data/sig/sxn/templates/template_engine.rbs +50 -0
- data/sig/sxn/templates/template_processor.rbs +44 -0
- data/sig/sxn/templates/template_security.rbs +62 -0
- data/sig/sxn/templates/template_variables.rbs +103 -0
- data/sig/sxn/templates.rbs +104 -0
- data/sig/sxn/ui/output.rbs +50 -0
- data/sig/sxn/ui/progress_bar.rbs +39 -0
- data/sig/sxn/ui/prompt.rbs +38 -0
- data/sig/sxn/ui/table.rbs +43 -0
- data/sig/sxn/ui.rbs +63 -0
- data/sig/sxn/version.rbs +5 -0
- data/sig/sxn.rbs +29 -0
- metadata +635 -0
data/README.md
ADDED
@@ -0,0 +1,225 @@
|
|
1
|
+
# Sxn
|
2
|
+
|
3
|
+
[](https://github.com/idl3/sxn/actions/workflows/ci.yml)
|
4
|
+
[](https://www.ruby-lang.org)
|
5
|
+
[](LICENSE.txt)
|
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.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Session Management**: Create isolated development sessions with their own git worktrees
|
12
|
+
- **Multi-Repository Support**: Work with multiple repositories in a single session
|
13
|
+
- **Automatic Project Setup**: Apply project-specific rules and templates automatically
|
14
|
+
- **Git Worktree Integration**: Leverage git worktrees for efficient branch management
|
15
|
+
- **Template Engine**: Generate project-specific files using Liquid templates
|
16
|
+
- **Security First**: Path validation and command sanitization for safe operations
|
17
|
+
- **Thread-Safe**: Concurrent operations with proper synchronization
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'sxn'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
```bash
|
30
|
+
bundle install
|
31
|
+
```
|
32
|
+
|
33
|
+
Or install it yourself as:
|
34
|
+
|
35
|
+
```bash
|
36
|
+
gem install sxn
|
37
|
+
```
|
38
|
+
|
39
|
+
## Quick Start
|
40
|
+
|
41
|
+
### Initialize Sxn in your workspace
|
42
|
+
|
43
|
+
```bash
|
44
|
+
sxn init
|
45
|
+
```
|
46
|
+
|
47
|
+
### Create a new session
|
48
|
+
|
49
|
+
```bash
|
50
|
+
sxn add feature-xyz --description "Working on feature XYZ"
|
51
|
+
```
|
52
|
+
|
53
|
+
### Switch to a session
|
54
|
+
|
55
|
+
```bash
|
56
|
+
sxn use feature-xyz
|
57
|
+
```
|
58
|
+
|
59
|
+
### Add a project worktree to current session
|
60
|
+
|
61
|
+
```bash
|
62
|
+
sxn worktree add my-project --branch feature-xyz
|
63
|
+
```
|
64
|
+
|
65
|
+
### List sessions
|
66
|
+
|
67
|
+
```bash
|
68
|
+
sxn list
|
69
|
+
```
|
70
|
+
|
71
|
+
## Usage
|
72
|
+
|
73
|
+
### Session Management
|
74
|
+
|
75
|
+
Sessions are isolated workspaces that contain git worktrees for your projects:
|
76
|
+
|
77
|
+
```bash
|
78
|
+
# Create a new session
|
79
|
+
sxn add my-feature
|
80
|
+
|
81
|
+
# Switch to a session
|
82
|
+
sxn use my-feature
|
83
|
+
|
84
|
+
# List all sessions
|
85
|
+
sxn list
|
86
|
+
|
87
|
+
# Show current session
|
88
|
+
sxn current
|
89
|
+
|
90
|
+
# Remove a session
|
91
|
+
sxn sessions remove my-feature
|
92
|
+
```
|
93
|
+
|
94
|
+
### Project Management
|
95
|
+
|
96
|
+
Register and manage projects that can be added to sessions:
|
97
|
+
|
98
|
+
```bash
|
99
|
+
# Add a project
|
100
|
+
sxn projects add my-app ~/projects/my-app
|
101
|
+
|
102
|
+
# List projects
|
103
|
+
sxn projects list
|
104
|
+
|
105
|
+
# Remove a project
|
106
|
+
sxn projects remove my-app
|
107
|
+
```
|
108
|
+
|
109
|
+
### Worktree Management
|
110
|
+
|
111
|
+
Add project worktrees to your current session:
|
112
|
+
|
113
|
+
```bash
|
114
|
+
# Add a worktree for a project
|
115
|
+
sxn worktree add my-app --branch feature-branch
|
116
|
+
|
117
|
+
# List worktrees in current session
|
118
|
+
sxn worktree list
|
119
|
+
|
120
|
+
# Remove a worktree
|
121
|
+
sxn worktree remove my-app
|
122
|
+
```
|
123
|
+
|
124
|
+
### Rules and Templates
|
125
|
+
|
126
|
+
Define project-specific setup rules:
|
127
|
+
|
128
|
+
```bash
|
129
|
+
# List available rules
|
130
|
+
sxn rules list
|
131
|
+
|
132
|
+
# Apply rules to a project
|
133
|
+
sxn rules apply my-app
|
134
|
+
```
|
135
|
+
|
136
|
+
## Configuration
|
137
|
+
|
138
|
+
Sxn stores its configuration in `.sxn/config.yml` in your workspace:
|
139
|
+
|
140
|
+
```yaml
|
141
|
+
sessions_folder: .sxn-sessions
|
142
|
+
settings:
|
143
|
+
auto_cleanup: true
|
144
|
+
max_sessions: 10
|
145
|
+
default_branch: main
|
146
|
+
```
|
147
|
+
|
148
|
+
## Project Rules
|
149
|
+
|
150
|
+
Create `.sxn-rules.yml` in your project root to define automatic setup:
|
151
|
+
|
152
|
+
```yaml
|
153
|
+
rules:
|
154
|
+
- type: template
|
155
|
+
template: rails/database.yml
|
156
|
+
destination: config/database.yml
|
157
|
+
|
158
|
+
- type: copy_files
|
159
|
+
source: .env.example
|
160
|
+
destination: .env
|
161
|
+
|
162
|
+
- type: setup_commands
|
163
|
+
commands:
|
164
|
+
- bundle install
|
165
|
+
- yarn install
|
166
|
+
- rails db:setup
|
167
|
+
```
|
168
|
+
|
169
|
+
## Templates
|
170
|
+
|
171
|
+
Sxn includes templates for common project types:
|
172
|
+
|
173
|
+
- **Rails**: CLAUDE.md, database.yml, session-info.md
|
174
|
+
- **JavaScript**: README.md, session-info.md
|
175
|
+
- **Common**: .gitignore, session-info.md
|
176
|
+
|
177
|
+
Templates use Liquid syntax and have access to session, project, and environment variables.
|
178
|
+
|
179
|
+
## Development
|
180
|
+
|
181
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests.
|
182
|
+
|
183
|
+
### Running Tests
|
184
|
+
|
185
|
+
```bash
|
186
|
+
# Run all tests
|
187
|
+
bundle exec rspec
|
188
|
+
|
189
|
+
# Run only unit tests
|
190
|
+
bundle exec rspec spec/unit
|
191
|
+
|
192
|
+
# Run with coverage
|
193
|
+
ENABLE_SIMPLECOV=true bundle exec rspec
|
194
|
+
```
|
195
|
+
|
196
|
+
### Type Checking
|
197
|
+
|
198
|
+
```bash
|
199
|
+
# Install RBS dependencies
|
200
|
+
rbs collection install
|
201
|
+
|
202
|
+
# Run Steep type checker
|
203
|
+
steep check
|
204
|
+
```
|
205
|
+
|
206
|
+
### Linting
|
207
|
+
|
208
|
+
```bash
|
209
|
+
# Run RuboCop
|
210
|
+
bundle exec rubocop
|
211
|
+
```
|
212
|
+
|
213
|
+
## Contributing
|
214
|
+
|
215
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/yourusername/sxn.
|
216
|
+
|
217
|
+
1. Fork it
|
218
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
219
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
220
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
221
|
+
5. Create a new Pull Request
|
222
|
+
|
223
|
+
## License
|
224
|
+
|
225
|
+
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rubocop/rake_task"
|
5
|
+
|
6
|
+
RuboCop::RakeTask.new
|
7
|
+
|
8
|
+
# RSpec tasks
|
9
|
+
begin
|
10
|
+
require "rspec/core/rake_task"
|
11
|
+
RSpec::Core::RakeTask.new(:spec)
|
12
|
+
rescue LoadError
|
13
|
+
# RSpec not available
|
14
|
+
end
|
15
|
+
|
16
|
+
# Type checking tasks
|
17
|
+
namespace :rbs do
|
18
|
+
desc "Validate RBS files syntax"
|
19
|
+
task :validate do
|
20
|
+
sh "bundle exec rbs validate"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Run type checking with Steep"
|
24
|
+
task :check do
|
25
|
+
sh "bundle exec steep check"
|
26
|
+
end
|
27
|
+
|
28
|
+
desc "Generate RBS prototype from Ruby files"
|
29
|
+
task :prototype do
|
30
|
+
sh "bundle exec rbs prototype rb lib/**/*.rb > sig/generated.rbs"
|
31
|
+
end
|
32
|
+
|
33
|
+
desc "Show Steep statistics"
|
34
|
+
task :stats do
|
35
|
+
sh "bundle exec steep stats"
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Setup RBS collection"
|
39
|
+
task :collection do
|
40
|
+
sh "bundle exec rbs collection install"
|
41
|
+
end
|
42
|
+
|
43
|
+
desc "Run RBS test with runtime type checking"
|
44
|
+
task :test do
|
45
|
+
ENV["RBS_TEST_TARGET"] = "Sxn::*"
|
46
|
+
Rake::Task["spec"].invoke
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
desc "Run all quality checks (rubocop + type checking)"
|
51
|
+
task quality: [:rubocop, "rbs:validate", "rbs:check"]
|
52
|
+
|
53
|
+
desc "Run tests"
|
54
|
+
task default: :spec
|
data/Steepfile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
D = Steep::Diagnostic
|
4
|
+
|
5
|
+
target :lib do
|
6
|
+
signature "sig"
|
7
|
+
|
8
|
+
check "lib"
|
9
|
+
|
10
|
+
# Ignore non-Ruby files
|
11
|
+
ignore "lib/sxn/templates/**/*.liquid"
|
12
|
+
|
13
|
+
# Standard library types
|
14
|
+
library "pathname", "logger", "json", "yaml", "fileutils", "optparse"
|
15
|
+
library "tempfile", "digest", "time", "shellwords", "open3", "stringio"
|
16
|
+
library "monitor", "mutex_m", "timeout", "forwardable"
|
17
|
+
|
18
|
+
# Configure diagnostic settings
|
19
|
+
configure_code_diagnostics do |hash|
|
20
|
+
# Critical errors that must be fixed
|
21
|
+
hash[D::Ruby::MethodArityMismatch] = :error
|
22
|
+
hash[D::Ruby::RequiredBlockMissing] = :error
|
23
|
+
hash[D::Ruby::InsufficientKeywordArguments] = :error
|
24
|
+
hash[D::Ruby::InsufficientPositionalArguments] = :error
|
25
|
+
hash[D::Ruby::ReturnTypeMismatch] = :error
|
26
|
+
hash[D::Ruby::MethodBodyTypeMismatch] = :warning
|
27
|
+
|
28
|
+
# Framework limitations and metaprogramming
|
29
|
+
hash[D::Ruby::UnexpectedKeywordArgument] = :information # Thor dynamic args
|
30
|
+
hash[D::Ruby::UnexpectedPositionalArgument] = :information # Thor dynamic args
|
31
|
+
hash[D::Ruby::FallbackAny] = :hint # Template variable resolution
|
32
|
+
hash[D::Ruby::NoMethod] = :hint # Dynamic method calls
|
33
|
+
|
34
|
+
# RBS coverage gaps
|
35
|
+
hash[D::Ruby::UnknownConstant] = :hint
|
36
|
+
hash[D::Ruby::MethodDefinitionMissing] = :hint
|
37
|
+
hash[D::Ruby::UndeclaredMethodDefinition] = :hint
|
38
|
+
|
39
|
+
# Type coercion
|
40
|
+
hash[D::Ruby::ArgumentTypeMismatch] = :information
|
41
|
+
hash[D::Ruby::IncompatibleAssignment] = :warning
|
42
|
+
hash[D::Ruby::MethodReturnTypeAnnotationMismatch] = :warning
|
43
|
+
|
44
|
+
# Other warnings
|
45
|
+
hash[D::Ruby::UnexpectedBlockGiven] = :warning
|
46
|
+
hash[D::Ruby::UnresolvedOverloading] = :warning
|
47
|
+
hash[D::Ruby::UnexpectedJump] = :hint
|
48
|
+
hash[D::Ruby::UnannotatedEmptyCollection] = :hint
|
49
|
+
end
|
50
|
+
end
|
data/bin/sxn
ADDED
data/lib/sxn/CLI.rb
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module Sxn
|
6
|
+
# Main CLI class using Thor framework
|
7
|
+
class CLI < Thor
|
8
|
+
class_option :verbose, type: :boolean, aliases: "-v", desc: "Enable verbose output"
|
9
|
+
class_option :config, type: :string, aliases: "-c", desc: "Path to configuration file"
|
10
|
+
|
11
|
+
def self.exit_on_failure?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(args = ARGV, local_options = {}, config = {})
|
16
|
+
super
|
17
|
+
@ui = Sxn::UI::Output.new
|
18
|
+
setup_environment
|
19
|
+
end
|
20
|
+
|
21
|
+
desc "version", "Show version information"
|
22
|
+
def version
|
23
|
+
puts "sxn #{Sxn::VERSION}"
|
24
|
+
puts "Session management for multi-repository development"
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "init [FOLDER]", "Initialize sxn in a project folder"
|
28
|
+
option :force, type: :boolean, desc: "Force initialization even if already initialized"
|
29
|
+
option :auto_detect, type: :boolean, default: true, desc: "Automatically detect and register projects"
|
30
|
+
option :quiet, type: :boolean, aliases: "-q", desc: "Suppress interactive prompts"
|
31
|
+
def init(folder = nil)
|
32
|
+
# steep:ignore:start - Thor dynamic argument validation handled at runtime
|
33
|
+
# Thor framework uses metaprogramming for argument parsing that can't be statically typed.
|
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
|
+
Commands::Init.new.init(folder)
|
45
|
+
rescue Sxn::Error => e
|
46
|
+
handle_error(e)
|
47
|
+
end
|
48
|
+
|
49
|
+
desc "add SESSION_NAME", "Create a new session (shortcut for 'sxn sessions add')"
|
50
|
+
option :description, type: :string, aliases: "-d", desc: "Session description"
|
51
|
+
option :linear_task, type: :string, aliases: "-l", desc: "Linear task ID"
|
52
|
+
def add(session_name)
|
53
|
+
Commands::Sessions.new.add(session_name)
|
54
|
+
rescue Sxn::Error => e
|
55
|
+
handle_error(e)
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "use SESSION_NAME", "Switch to a session (shortcut for 'sxn sessions use')"
|
59
|
+
def use(session_name)
|
60
|
+
Commands::Sessions.new.use(session_name)
|
61
|
+
rescue Sxn::Error => e
|
62
|
+
handle_error(e)
|
63
|
+
end
|
64
|
+
|
65
|
+
desc "list", "List sessions (shortcut for 'sxn sessions list')"
|
66
|
+
option :status, type: :string, enum: %w[active inactive archived], desc: "Filter by status"
|
67
|
+
def list
|
68
|
+
Commands::Sessions.new.list
|
69
|
+
rescue Sxn::Error => e
|
70
|
+
handle_error(e)
|
71
|
+
end
|
72
|
+
|
73
|
+
desc "current", "Show current session (shortcut for 'sxn sessions current')"
|
74
|
+
option :verbose, type: :boolean, aliases: "-v", desc: "Show detailed information"
|
75
|
+
def current
|
76
|
+
Commands::Sessions.new.current
|
77
|
+
rescue Sxn::Error => e
|
78
|
+
handle_error(e)
|
79
|
+
end
|
80
|
+
|
81
|
+
desc "projects SUBCOMMAND", "Manage project configurations"
|
82
|
+
def projects(subcommand = nil, *args)
|
83
|
+
Commands::Projects.start([subcommand, *args].compact)
|
84
|
+
rescue Sxn::Error => e
|
85
|
+
handle_error(e)
|
86
|
+
end
|
87
|
+
|
88
|
+
desc "sessions SUBCOMMAND", "Manage development sessions"
|
89
|
+
def sessions(subcommand = nil, *args)
|
90
|
+
Commands::Sessions.start([subcommand, *args].compact)
|
91
|
+
rescue Sxn::Error => e
|
92
|
+
handle_error(e)
|
93
|
+
end
|
94
|
+
|
95
|
+
desc "worktree SUBCOMMAND", "Manage git worktrees"
|
96
|
+
def worktree(subcommand = nil, *args)
|
97
|
+
Commands::Worktrees.start([subcommand, *args].compact)
|
98
|
+
rescue Sxn::Error => e
|
99
|
+
handle_error(e)
|
100
|
+
end
|
101
|
+
|
102
|
+
desc "rules SUBCOMMAND", "Manage project setup rules"
|
103
|
+
def rules(subcommand = nil, *args)
|
104
|
+
Commands::Rules.start([subcommand, *args].compact)
|
105
|
+
rescue Sxn::Error => e
|
106
|
+
handle_error(e)
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "status", "Show overall sxn status"
|
110
|
+
def status
|
111
|
+
show_status
|
112
|
+
rescue Sxn::Error => e
|
113
|
+
handle_error(e)
|
114
|
+
end
|
115
|
+
|
116
|
+
desc "config", "Show configuration information"
|
117
|
+
option :validate, type: :boolean, aliases: "-v", desc: "Validate configuration"
|
118
|
+
def config
|
119
|
+
show_config
|
120
|
+
rescue Sxn::Error => e
|
121
|
+
handle_error(e)
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def setup_environment
|
127
|
+
ENV["SXN_DEBUG"] = "true" if options[:verbose]
|
128
|
+
|
129
|
+
# Set custom config path if provided
|
130
|
+
ENV["SXN_CONFIG_PATH"] = File.expand_path(options[:config]) if options[:config]
|
131
|
+
|
132
|
+
# Setup logger based on debug environment
|
133
|
+
if ENV["SXN_DEBUG"]
|
134
|
+
Sxn.setup_logger(level: :debug)
|
135
|
+
else
|
136
|
+
Sxn.setup_logger(level: :info)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def handle_error(error)
|
141
|
+
case error
|
142
|
+
when Sxn::ConfigurationError
|
143
|
+
@ui.error(error.message)
|
144
|
+
@ui.recovery_suggestion("Run 'sxn init' to initialize sxn in this project")
|
145
|
+
when Sxn::SessionNotFoundError
|
146
|
+
@ui.error(error.message)
|
147
|
+
@ui.recovery_suggestion("List available sessions with 'sxn list'")
|
148
|
+
when Sxn::ProjectNotFoundError
|
149
|
+
@ui.error(error.message)
|
150
|
+
@ui.recovery_suggestion("List available projects with 'sxn projects list'")
|
151
|
+
when Sxn::NoActiveSessionError
|
152
|
+
@ui.error(error.message)
|
153
|
+
@ui.recovery_suggestion("Activate a session with 'sxn use <session>' or create one with 'sxn add <session>'")
|
154
|
+
when Sxn::WorktreeNotFoundError
|
155
|
+
@ui.error(error.message)
|
156
|
+
@ui.recovery_suggestion("List worktrees with 'sxn worktree list' or add one with 'sxn worktree add <project>'")
|
157
|
+
when Sxn::SecurityError, Sxn::PathValidationError
|
158
|
+
@ui.error("Security error: #{error.message}")
|
159
|
+
@ui.warning("This operation was blocked for security reasons")
|
160
|
+
when Sxn::GitError, Sxn::WorktreeError
|
161
|
+
@ui.error("Git error: #{error.message}")
|
162
|
+
@ui.recovery_suggestion("Check git repository status and try again")
|
163
|
+
else
|
164
|
+
@ui.error(error.message)
|
165
|
+
@ui.debug(error.backtrace.join("\n")) if ENV["SXN_DEBUG"]
|
166
|
+
end
|
167
|
+
|
168
|
+
exit(error.exit_code)
|
169
|
+
end
|
170
|
+
|
171
|
+
def show_status
|
172
|
+
config_manager = Sxn::Core::ConfigManager.new
|
173
|
+
|
174
|
+
unless config_manager.initialized?
|
175
|
+
@ui.error("Not initialized")
|
176
|
+
@ui.recovery_suggestion("Run 'sxn init' to initialize sxn in this project")
|
177
|
+
return
|
178
|
+
end
|
179
|
+
|
180
|
+
@ui.section("Sxn Status")
|
181
|
+
|
182
|
+
# Current session
|
183
|
+
current_session = config_manager.current_session
|
184
|
+
if current_session
|
185
|
+
@ui.key_value("Current Session", current_session)
|
186
|
+
else
|
187
|
+
@ui.key_value("Current Session", "None")
|
188
|
+
end
|
189
|
+
|
190
|
+
# Sessions folder
|
191
|
+
sessions_folder = config_manager.sessions_folder_path
|
192
|
+
@ui.key_value("Sessions Folder", sessions_folder)
|
193
|
+
|
194
|
+
# Quick stats
|
195
|
+
session_manager = Sxn::Core::SessionManager.new(config_manager)
|
196
|
+
project_manager = Sxn::Core::ProjectManager.new(config_manager)
|
197
|
+
|
198
|
+
sessions = session_manager.list_sessions
|
199
|
+
projects = project_manager.list_projects
|
200
|
+
|
201
|
+
# steep:ignore:start - Safe integer to string coercion for UI display
|
202
|
+
# These integer values are safely converted to strings for display purposes.
|
203
|
+
# Runtime validation ensures proper type handling.
|
204
|
+
@ui.key_value("Total Sessions",
|
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"))
|
208
|
+
|
209
|
+
# Active worktrees
|
210
|
+
if current_session
|
211
|
+
worktree_manager = Sxn::Core::WorktreeManager.new(config_manager, session_manager)
|
212
|
+
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"))
|
215
|
+
end
|
216
|
+
|
217
|
+
@ui.newline
|
218
|
+
@ui.subsection("Quick Commands")
|
219
|
+
|
220
|
+
if current_session
|
221
|
+
@ui.command_example("sxn worktree add <project>", "Add worktree to current session")
|
222
|
+
@ui.command_example("sxn worktree list", "List worktrees in current session")
|
223
|
+
else
|
224
|
+
@ui.command_example("sxn add <session>", "Create a new session")
|
225
|
+
@ui.command_example("sxn list", "List all sessions")
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def show_config
|
230
|
+
config_manager = Sxn::Core::ConfigManager.new
|
231
|
+
|
232
|
+
unless config_manager.initialized?
|
233
|
+
@ui.error("Not initialized")
|
234
|
+
@ui.recovery_suggestion("Run 'sxn init' to initialize sxn in this project")
|
235
|
+
return
|
236
|
+
end
|
237
|
+
|
238
|
+
@ui.section("Configuration")
|
239
|
+
|
240
|
+
begin
|
241
|
+
config = config_manager.get_config
|
242
|
+
table = Sxn::UI::Table.new
|
243
|
+
table.config_summary({
|
244
|
+
sessions_folder: config.sessions_folder,
|
245
|
+
current_session: config_manager.current_session,
|
246
|
+
auto_cleanup: config.settings&.auto_cleanup,
|
247
|
+
max_sessions: config.settings&.max_sessions
|
248
|
+
})
|
249
|
+
|
250
|
+
if options[:validate]
|
251
|
+
@ui.subsection("Validation")
|
252
|
+
|
253
|
+
# Validate configuration
|
254
|
+
issues = [] # : Array[String]
|
255
|
+
|
256
|
+
unless File.directory?(config_manager.sessions_folder_path)
|
257
|
+
issues << "Sessions folder does not exist: #{config_manager.sessions_folder_path}"
|
258
|
+
end
|
259
|
+
|
260
|
+
issues << "Configuration file is not readable: #{config_manager.config_path}" unless File.readable?(config_manager.config_path)
|
261
|
+
|
262
|
+
if issues.empty?
|
263
|
+
@ui.success("Configuration is valid")
|
264
|
+
else
|
265
|
+
@ui.error("Configuration issues found:")
|
266
|
+
issues.each { |issue| @ui.list_item(issue) }
|
267
|
+
end
|
268
|
+
end
|
269
|
+
rescue StandardError => e
|
270
|
+
@ui.error("Could not load configuration: #{e.message}")
|
271
|
+
@ui.debug(e.backtrace.join("\n")) if ENV["SXN_DEBUG"]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|