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.
Files changed (156) hide show
  1. checksums.yaml +7 -0
  2. data/.gem_rbs_collection/addressable/2.8/.rbs_meta.yaml +9 -0
  3. data/.gem_rbs_collection/addressable/2.8/addressable.rbs +62 -0
  4. data/.gem_rbs_collection/async/2.12/.rbs_meta.yaml +9 -0
  5. data/.gem_rbs_collection/async/2.12/async.rbs +119 -0
  6. data/.gem_rbs_collection/async/2.12/kernel.rbs +5 -0
  7. data/.gem_rbs_collection/async/2.12/manifest.yaml +7 -0
  8. data/.gem_rbs_collection/bcrypt/3.1/.rbs_meta.yaml +9 -0
  9. data/.gem_rbs_collection/bcrypt/3.1/bcrypt.rbs +47 -0
  10. data/.gem_rbs_collection/bcrypt/3.1/manifest.yaml +2 -0
  11. data/.gem_rbs_collection/bigdecimal/3.1/.rbs_meta.yaml +9 -0
  12. data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal-math.rbs +119 -0
  13. data/.gem_rbs_collection/bigdecimal/3.1/bigdecimal.rbs +1630 -0
  14. data/.gem_rbs_collection/concurrent-ruby/1.1/.rbs_meta.yaml +9 -0
  15. data/.gem_rbs_collection/concurrent-ruby/1.1/array.rbs +4 -0
  16. data/.gem_rbs_collection/concurrent-ruby/1.1/executor.rbs +26 -0
  17. data/.gem_rbs_collection/concurrent-ruby/1.1/hash.rbs +4 -0
  18. data/.gem_rbs_collection/concurrent-ruby/1.1/map.rbs +65 -0
  19. data/.gem_rbs_collection/concurrent-ruby/1.1/promises.rbs +249 -0
  20. data/.gem_rbs_collection/concurrent-ruby/1.1/utility/processor_counter.rbs +5 -0
  21. data/.gem_rbs_collection/diff-lcs/1.5/.rbs_meta.yaml +9 -0
  22. data/.gem_rbs_collection/diff-lcs/1.5/diff-lcs.rbs +11 -0
  23. data/.gem_rbs_collection/listen/3.9/.rbs_meta.yaml +9 -0
  24. data/.gem_rbs_collection/listen/3.9/listen.rbs +25 -0
  25. data/.gem_rbs_collection/listen/3.9/listener.rbs +24 -0
  26. data/.gem_rbs_collection/mini_mime/0.1/.rbs_meta.yaml +9 -0
  27. data/.gem_rbs_collection/mini_mime/0.1/mini_mime.rbs +14 -0
  28. data/.gem_rbs_collection/parallel/1.20/.rbs_meta.yaml +9 -0
  29. data/.gem_rbs_collection/parallel/1.20/parallel.rbs +86 -0
  30. data/.gem_rbs_collection/rake/13.0/.rbs_meta.yaml +9 -0
  31. data/.gem_rbs_collection/rake/13.0/manifest.yaml +2 -0
  32. data/.gem_rbs_collection/rake/13.0/rake.rbs +39 -0
  33. data/.gem_rbs_collection/rubocop-ast/1.46/.rbs_meta.yaml +9 -0
  34. data/.gem_rbs_collection/rubocop-ast/1.46/rubocop-ast.rbs +822 -0
  35. data/.gem_rbs_collection/sqlite3/2.0/.rbs_meta.yaml +9 -0
  36. data/.gem_rbs_collection/sqlite3/2.0/database.rbs +20 -0
  37. data/.gem_rbs_collection/sqlite3/2.0/pragmas.rbs +5 -0
  38. data/.rspec +4 -0
  39. data/.rubocop.yml +121 -0
  40. data/.simplecov +51 -0
  41. data/CHANGELOG.md +49 -0
  42. data/Gemfile +24 -0
  43. data/Gemfile.lock +329 -0
  44. data/LICENSE.txt +21 -0
  45. data/README.md +225 -0
  46. data/Rakefile +54 -0
  47. data/Steepfile +50 -0
  48. data/bin/sxn +6 -0
  49. data/lib/sxn/CLI.rb +275 -0
  50. data/lib/sxn/commands/init.rb +137 -0
  51. data/lib/sxn/commands/projects.rb +350 -0
  52. data/lib/sxn/commands/rules.rb +435 -0
  53. data/lib/sxn/commands/sessions.rb +300 -0
  54. data/lib/sxn/commands/worktrees.rb +416 -0
  55. data/lib/sxn/commands.rb +13 -0
  56. data/lib/sxn/config/config_cache.rb +295 -0
  57. data/lib/sxn/config/config_discovery.rb +242 -0
  58. data/lib/sxn/config/config_validator.rb +562 -0
  59. data/lib/sxn/config.rb +259 -0
  60. data/lib/sxn/core/config_manager.rb +290 -0
  61. data/lib/sxn/core/project_manager.rb +307 -0
  62. data/lib/sxn/core/rules_manager.rb +306 -0
  63. data/lib/sxn/core/session_manager.rb +336 -0
  64. data/lib/sxn/core/worktree_manager.rb +281 -0
  65. data/lib/sxn/core.rb +13 -0
  66. data/lib/sxn/database/errors.rb +29 -0
  67. data/lib/sxn/database/session_database.rb +691 -0
  68. data/lib/sxn/database.rb +24 -0
  69. data/lib/sxn/errors.rb +76 -0
  70. data/lib/sxn/rules/base_rule.rb +367 -0
  71. data/lib/sxn/rules/copy_files_rule.rb +346 -0
  72. data/lib/sxn/rules/errors.rb +28 -0
  73. data/lib/sxn/rules/project_detector.rb +871 -0
  74. data/lib/sxn/rules/rules_engine.rb +485 -0
  75. data/lib/sxn/rules/setup_commands_rule.rb +307 -0
  76. data/lib/sxn/rules/template_rule.rb +262 -0
  77. data/lib/sxn/rules.rb +148 -0
  78. data/lib/sxn/runtime_validations.rb +96 -0
  79. data/lib/sxn/security/secure_command_executor.rb +364 -0
  80. data/lib/sxn/security/secure_file_copier.rb +478 -0
  81. data/lib/sxn/security/secure_path_validator.rb +258 -0
  82. data/lib/sxn/security.rb +15 -0
  83. data/lib/sxn/templates/common/gitignore.liquid +99 -0
  84. data/lib/sxn/templates/common/session-info.md.liquid +58 -0
  85. data/lib/sxn/templates/errors.rb +36 -0
  86. data/lib/sxn/templates/javascript/README.md.liquid +59 -0
  87. data/lib/sxn/templates/javascript/session-info.md.liquid +206 -0
  88. data/lib/sxn/templates/rails/CLAUDE.md.liquid +78 -0
  89. data/lib/sxn/templates/rails/database.yml.liquid +31 -0
  90. data/lib/sxn/templates/rails/session-info.md.liquid +144 -0
  91. data/lib/sxn/templates/template_engine.rb +346 -0
  92. data/lib/sxn/templates/template_processor.rb +279 -0
  93. data/lib/sxn/templates/template_security.rb +410 -0
  94. data/lib/sxn/templates/template_variables.rb +713 -0
  95. data/lib/sxn/templates.rb +28 -0
  96. data/lib/sxn/ui/output.rb +103 -0
  97. data/lib/sxn/ui/progress_bar.rb +91 -0
  98. data/lib/sxn/ui/prompt.rb +116 -0
  99. data/lib/sxn/ui/table.rb +183 -0
  100. data/lib/sxn/ui.rb +12 -0
  101. data/lib/sxn/version.rb +5 -0
  102. data/lib/sxn.rb +63 -0
  103. data/rbs_collection.lock.yaml +180 -0
  104. data/rbs_collection.yaml +39 -0
  105. data/scripts/test.sh +31 -0
  106. data/sig/external/liquid.rbs +116 -0
  107. data/sig/external/thor.rbs +99 -0
  108. data/sig/external/tty.rbs +71 -0
  109. data/sig/sxn/cli.rbs +46 -0
  110. data/sig/sxn/commands/init.rbs +38 -0
  111. data/sig/sxn/commands/projects.rbs +72 -0
  112. data/sig/sxn/commands/rules.rbs +95 -0
  113. data/sig/sxn/commands/sessions.rbs +62 -0
  114. data/sig/sxn/commands/worktrees.rbs +82 -0
  115. data/sig/sxn/commands.rbs +6 -0
  116. data/sig/sxn/config/config_cache.rbs +67 -0
  117. data/sig/sxn/config/config_discovery.rbs +64 -0
  118. data/sig/sxn/config/config_validator.rbs +64 -0
  119. data/sig/sxn/config.rbs +74 -0
  120. data/sig/sxn/core/config_manager.rbs +67 -0
  121. data/sig/sxn/core/project_manager.rbs +52 -0
  122. data/sig/sxn/core/rules_manager.rbs +54 -0
  123. data/sig/sxn/core/session_manager.rbs +59 -0
  124. data/sig/sxn/core/worktree_manager.rbs +50 -0
  125. data/sig/sxn/core.rbs +87 -0
  126. data/sig/sxn/database/errors.rbs +37 -0
  127. data/sig/sxn/database/session_database.rbs +151 -0
  128. data/sig/sxn/database.rbs +83 -0
  129. data/sig/sxn/errors.rbs +89 -0
  130. data/sig/sxn/rules/base_rule.rbs +137 -0
  131. data/sig/sxn/rules/copy_files_rule.rbs +65 -0
  132. data/sig/sxn/rules/errors.rbs +33 -0
  133. data/sig/sxn/rules/project_detector.rbs +115 -0
  134. data/sig/sxn/rules/rules_engine.rbs +118 -0
  135. data/sig/sxn/rules/setup_commands_rule.rbs +60 -0
  136. data/sig/sxn/rules/template_rule.rbs +44 -0
  137. data/sig/sxn/rules.rbs +287 -0
  138. data/sig/sxn/runtime_validations.rbs +16 -0
  139. data/sig/sxn/security/secure_command_executor.rbs +63 -0
  140. data/sig/sxn/security/secure_file_copier.rbs +79 -0
  141. data/sig/sxn/security/secure_path_validator.rbs +30 -0
  142. data/sig/sxn/security.rbs +128 -0
  143. data/sig/sxn/templates/errors.rbs +43 -0
  144. data/sig/sxn/templates/template_engine.rbs +50 -0
  145. data/sig/sxn/templates/template_processor.rbs +44 -0
  146. data/sig/sxn/templates/template_security.rbs +62 -0
  147. data/sig/sxn/templates/template_variables.rbs +103 -0
  148. data/sig/sxn/templates.rbs +104 -0
  149. data/sig/sxn/ui/output.rbs +50 -0
  150. data/sig/sxn/ui/progress_bar.rbs +39 -0
  151. data/sig/sxn/ui/prompt.rbs +38 -0
  152. data/sig/sxn/ui/table.rbs +43 -0
  153. data/sig/sxn/ui.rbs +63 -0
  154. data/sig/sxn/version.rbs +5 -0
  155. data/sig/sxn.rbs +29 -0
  156. metadata +635 -0
data/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # Sxn
2
+
3
+ [![CI](https://github.com/idl3/sxn/actions/workflows/ci.yml/badge.svg)](https://github.com/idl3/sxn/actions/workflows/ci.yml)
4
+ [![Ruby Version](https://img.shields.io/badge/ruby-3.2%2B-red)](https://www.ruby-lang.org)
5
+ [![License](https://img.shields.io/badge/license-MIT-blue)](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
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/sxn"
5
+
6
+ Sxn::CLI.start(ARGV)
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