sxn 0.2.1 → 0.2.2
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/Gemfile +2 -1
- data/Gemfile.lock +4 -1
- data/README.md +4 -0
- 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/database/session_database.rb +56 -6
- data/lib/sxn/runtime_validations.rb +5 -1
- data/lib/sxn/version.rb +1 -1
- data/sig/sxn/cli.rbs +6 -0
- data/sig/sxn/commands/sessions.rbs +4 -0
- metadata +4 -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: b9758f35bc9f122c019b15dd8a46f712cda58eb86495c6e5bb8c63791b10525e
|
4
|
+
data.tar.gz: 628449e55edf8b33ade3f83cdfab4369234bd22e26e5625cc3890b3c46a74702
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7970fbb77d1b7b21da3b93d566bb54a42219c787ca081b2319ec6c3dcf7b31dee35761339da42322ac2acf393232fa458325e3573dfebec546caa2e14f9da0eb
|
7
|
+
data.tar.gz: bd4f880ea4b2aa55b1a0de1d87d6e6bcc41a787218d29c104c644c87d07d2c43048b77217ba6ceae3f3df66a2da63c531a6b7f6cc96781d1454f0a2622232d1a
|
data/.parallel_rspec
ADDED
data/.rspec
CHANGED
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.2)
|
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
@@ -223,3 +223,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/yourus
|
|
223
223
|
## License
|
224
224
|
|
225
225
|
The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
|
226
|
+
|
227
|
+
## Author's Note
|
228
|
+
|
229
|
+
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
|
@@ -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/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.2
|
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
|
@@ -606,7 +609,6 @@ files:
|
|
606
609
|
- sig/sxn/ui/prompt.rbs
|
607
610
|
- sig/sxn/ui/table.rbs
|
608
611
|
- sig/sxn/version.rbs
|
609
|
-
- sxn.gemspec
|
610
612
|
homepage: https://github.com/idl3/sxn
|
611
613
|
licenses:
|
612
614
|
- 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
|