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
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module Sxn
|
6
|
+
module Commands
|
7
|
+
# Initialize sxn in a project folder
|
8
|
+
class Init < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
desc "init [FOLDER]", "Initialize sxn in a project folder"
|
12
|
+
option :force, type: :boolean, desc: "Force initialization even if already initialized"
|
13
|
+
option :auto_detect, type: :boolean, default: true, desc: "Automatically detect and register projects"
|
14
|
+
option :quiet, type: :boolean, aliases: "-q", desc: "Suppress interactive prompts"
|
15
|
+
|
16
|
+
def initialize(args = ARGV, local_options = {}, config = {})
|
17
|
+
super
|
18
|
+
@ui = Sxn::UI::Output.new
|
19
|
+
@prompt = Sxn::UI::Prompt.new
|
20
|
+
@config_manager = Sxn::Core::ConfigManager.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def init(folder = nil)
|
24
|
+
@ui.section("Sxn Initialization")
|
25
|
+
|
26
|
+
# Check if already initialized
|
27
|
+
if @config_manager.initialized? && !options[:force]
|
28
|
+
@ui.warning("Project already initialized")
|
29
|
+
@ui.info("Use --force to reinitialize")
|
30
|
+
return
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get sessions folder
|
34
|
+
sessions_folder = determine_sessions_folder(folder)
|
35
|
+
|
36
|
+
begin
|
37
|
+
# Initialize configuration
|
38
|
+
@ui.progress_start("Creating configuration")
|
39
|
+
result_folder = @config_manager.initialize_project(sessions_folder, force: options[:force])
|
40
|
+
@ui.progress_done
|
41
|
+
|
42
|
+
@ui.success("Initialized sxn in #{result_folder}")
|
43
|
+
|
44
|
+
# Auto-detect projects if enabled
|
45
|
+
auto_detect_projects if options[:auto_detect] && !options[:quiet]
|
46
|
+
|
47
|
+
display_next_steps
|
48
|
+
rescue Sxn::Error => e
|
49
|
+
@ui.error("Initialization failed: #{e.message}")
|
50
|
+
exit(e.exit_code)
|
51
|
+
rescue StandardError => e
|
52
|
+
@ui.error("Unexpected error: #{e.message}")
|
53
|
+
@ui.debug(e.backtrace.join("\n")) if ENV["SXN_DEBUG"]
|
54
|
+
exit(1)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def determine_sessions_folder(folder)
|
61
|
+
return folder if folder && !options[:quiet]
|
62
|
+
|
63
|
+
if options[:quiet]
|
64
|
+
# Use default folder in quiet mode
|
65
|
+
return folder || "#{File.basename(Dir.pwd)}-sessions"
|
66
|
+
end
|
67
|
+
|
68
|
+
# Interactive mode
|
69
|
+
@prompt.sessions_folder_setup
|
70
|
+
end
|
71
|
+
|
72
|
+
def auto_detect_projects
|
73
|
+
@ui.subsection("Project Detection")
|
74
|
+
|
75
|
+
detected = @config_manager.detect_projects
|
76
|
+
|
77
|
+
if detected.empty?
|
78
|
+
@ui.empty_state("No projects detected in current directory")
|
79
|
+
return
|
80
|
+
end
|
81
|
+
|
82
|
+
if @prompt.project_detection_confirm(detected)
|
83
|
+
register_detected_projects(detected)
|
84
|
+
else
|
85
|
+
@ui.info("Skipped project registration")
|
86
|
+
@ui.info("You can register projects later with: sxn projects add <name> <path>")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def register_detected_projects(projects)
|
91
|
+
project_manager = Sxn::Core::ProjectManager.new(@config_manager)
|
92
|
+
|
93
|
+
Sxn::UI::ProgressBar.with_progress("Registering projects", projects) do |project, progress|
|
94
|
+
result = project_manager.add_project(
|
95
|
+
project[:name],
|
96
|
+
project[:path],
|
97
|
+
type: project[:type]
|
98
|
+
)
|
99
|
+
|
100
|
+
progress.log("✅ #{project[:name]} (#{project[:type]})")
|
101
|
+
result
|
102
|
+
rescue StandardError => e
|
103
|
+
progress.log("❌ #{project[:name]}: #{e.message}")
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
|
107
|
+
@ui.success("Project registration completed")
|
108
|
+
end
|
109
|
+
|
110
|
+
def display_next_steps
|
111
|
+
@ui.newline
|
112
|
+
@ui.subsection("Next Steps")
|
113
|
+
|
114
|
+
@ui.command_example(
|
115
|
+
"sxn projects list",
|
116
|
+
"View registered projects"
|
117
|
+
)
|
118
|
+
|
119
|
+
@ui.command_example(
|
120
|
+
"sxn add my-session",
|
121
|
+
"Create your first session"
|
122
|
+
)
|
123
|
+
|
124
|
+
@ui.command_example(
|
125
|
+
"sxn worktree add <project> [branch]",
|
126
|
+
"Add a worktree to your session"
|
127
|
+
)
|
128
|
+
|
129
|
+
if @config_manager.detect_projects.any?
|
130
|
+
@ui.info("💡 Detected projects are ready to use!")
|
131
|
+
else
|
132
|
+
@ui.recovery_suggestion("Register your projects with 'sxn projects add <name> <path>'")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,350 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "thor"
|
4
|
+
|
5
|
+
module Sxn
|
6
|
+
module Commands
|
7
|
+
# Manage project configurations
|
8
|
+
class Projects < Thor
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
def initialize(args = ARGV, local_options = {}, config = {})
|
12
|
+
super
|
13
|
+
@ui = Sxn::UI::Output.new
|
14
|
+
@prompt = Sxn::UI::Prompt.new
|
15
|
+
@table = Sxn::UI::Table.new
|
16
|
+
@config_manager = Sxn::Core::ConfigManager.new
|
17
|
+
@project_manager = Sxn::Core::ProjectManager.new(@config_manager)
|
18
|
+
end
|
19
|
+
|
20
|
+
desc "add NAME PATH", "Add a project"
|
21
|
+
option :type, type: :string, desc: "Project type (rails, javascript, etc.)"
|
22
|
+
option :default_branch, type: :string, desc: "Default branch name"
|
23
|
+
option :interactive, type: :boolean, aliases: "-i", desc: "Interactive mode"
|
24
|
+
|
25
|
+
def add(name = nil, path = nil)
|
26
|
+
ensure_initialized!
|
27
|
+
|
28
|
+
# Interactive mode
|
29
|
+
if options[:interactive] || name.nil? || path.nil?
|
30
|
+
name ||= @prompt.project_name
|
31
|
+
path ||= @prompt.project_path
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
@ui.progress_start("Adding project '#{name}'")
|
36
|
+
|
37
|
+
project = @project_manager.add_project(
|
38
|
+
name,
|
39
|
+
path,
|
40
|
+
type: options[:type],
|
41
|
+
default_branch: options[:default_branch]
|
42
|
+
)
|
43
|
+
|
44
|
+
@ui.progress_done
|
45
|
+
@ui.success("Added project '#{name}'")
|
46
|
+
|
47
|
+
display_project_info(project)
|
48
|
+
rescue Sxn::Error => e
|
49
|
+
@ui.progress_failed
|
50
|
+
@ui.error(e.message)
|
51
|
+
exit(e.exit_code)
|
52
|
+
rescue StandardError => e
|
53
|
+
@ui.progress_failed
|
54
|
+
@ui.error("Unexpected error: #{e.message}")
|
55
|
+
@ui.debug(e.backtrace.join("\n")) if ENV["SXN_DEBUG"]
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
desc "remove NAME", "Remove a project"
|
61
|
+
option :force, type: :boolean, aliases: "-f", desc: "Force removal even if used in sessions"
|
62
|
+
|
63
|
+
def remove(name = nil)
|
64
|
+
ensure_initialized!
|
65
|
+
|
66
|
+
# Interactive selection if name not provided
|
67
|
+
if name.nil?
|
68
|
+
projects = @project_manager.list_projects
|
69
|
+
if projects.empty?
|
70
|
+
@ui.empty_state("No projects configured")
|
71
|
+
suggest_add_project
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
choices = projects.map { |p| { name: "#{p[:name]} (#{p[:type]})", value: p[:name] } }
|
76
|
+
name = @prompt.select("Select project to remove:", choices)
|
77
|
+
end
|
78
|
+
|
79
|
+
unless @prompt.confirm_deletion(name, "project")
|
80
|
+
@ui.info("Cancelled")
|
81
|
+
return
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
@ui.progress_start("Removing project '#{name}'")
|
86
|
+
@project_manager.remove_project(name)
|
87
|
+
@ui.progress_done
|
88
|
+
@ui.success("Removed project '#{name}'")
|
89
|
+
rescue Sxn::ProjectInUseError => e
|
90
|
+
@ui.progress_failed
|
91
|
+
@ui.error(e.message)
|
92
|
+
@ui.recovery_suggestion("Archive or remove the sessions first, or use --force")
|
93
|
+
exit(e.exit_code)
|
94
|
+
rescue Sxn::Error => e
|
95
|
+
@ui.progress_failed
|
96
|
+
@ui.error(e.message)
|
97
|
+
exit(e.exit_code)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "list", "List all projects"
|
102
|
+
option :validate, type: :boolean, aliases: "-v", desc: "Validate project paths"
|
103
|
+
|
104
|
+
def list
|
105
|
+
ensure_initialized!
|
106
|
+
|
107
|
+
begin
|
108
|
+
projects = @project_manager.list_projects
|
109
|
+
|
110
|
+
@ui.section("Registered Projects")
|
111
|
+
|
112
|
+
if projects.empty?
|
113
|
+
@ui.empty_state("No projects configured")
|
114
|
+
suggest_add_project
|
115
|
+
elsif options[:validate]
|
116
|
+
list_with_validation(projects)
|
117
|
+
else
|
118
|
+
@table.projects(projects)
|
119
|
+
@ui.newline
|
120
|
+
@ui.info("Total: #{projects.size} projects")
|
121
|
+
end
|
122
|
+
rescue Sxn::Error => e
|
123
|
+
@ui.error(e.message)
|
124
|
+
exit(e.exit_code)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
desc "scan [PATH]", "Scan for projects and optionally register them"
|
129
|
+
option :register, type: :boolean, aliases: "-r", desc: "Automatically register detected projects"
|
130
|
+
option :interactive, type: :boolean, aliases: "-i", default: true, desc: "Prompt before registering"
|
131
|
+
|
132
|
+
def scan(base_path = nil)
|
133
|
+
ensure_initialized!
|
134
|
+
|
135
|
+
base_path ||= Dir.pwd
|
136
|
+
|
137
|
+
begin
|
138
|
+
@ui.progress_start("Scanning for projects in #{base_path}")
|
139
|
+
detected = @project_manager.scan_projects(base_path)
|
140
|
+
@ui.progress_done
|
141
|
+
|
142
|
+
@ui.section("Detected Projects")
|
143
|
+
|
144
|
+
if detected.empty?
|
145
|
+
@ui.empty_state("No projects detected")
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
display_detected_projects(detected)
|
150
|
+
|
151
|
+
if options[:register]
|
152
|
+
register_projects(detected)
|
153
|
+
elsif options[:interactive]
|
154
|
+
register_projects(detected) if @prompt.ask_yes_no("Register detected projects?", default: true)
|
155
|
+
else
|
156
|
+
@ui.info("Use --register to add these projects automatically")
|
157
|
+
end
|
158
|
+
rescue Sxn::Error => e
|
159
|
+
@ui.error(e.message)
|
160
|
+
exit(e.exit_code)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
desc "validate NAME", "Validate a project configuration"
|
165
|
+
def validate(name = nil)
|
166
|
+
ensure_initialized!
|
167
|
+
|
168
|
+
if name.nil?
|
169
|
+
projects = @project_manager.list_projects
|
170
|
+
if projects.empty?
|
171
|
+
@ui.empty_state("No projects configured")
|
172
|
+
return
|
173
|
+
end
|
174
|
+
|
175
|
+
choices = projects.map { |p| { name: "#{p[:name]} (#{p[:type]})", value: p[:name] } }
|
176
|
+
name = @prompt.select("Select project to validate:", choices)
|
177
|
+
end
|
178
|
+
|
179
|
+
begin
|
180
|
+
result = @project_manager.validate_project(name)
|
181
|
+
|
182
|
+
@ui.section("Project Validation: #{name}")
|
183
|
+
|
184
|
+
if result[:valid]
|
185
|
+
@ui.success("Project is valid")
|
186
|
+
else
|
187
|
+
@ui.error("Project has issues:")
|
188
|
+
result[:issues].each { |issue| @ui.list_item(issue) }
|
189
|
+
end
|
190
|
+
|
191
|
+
display_project_info(result[:project])
|
192
|
+
rescue Sxn::Error => e
|
193
|
+
@ui.error(e.message)
|
194
|
+
exit(e.exit_code)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
desc "info NAME", "Show detailed project information"
|
199
|
+
def info(name = nil)
|
200
|
+
ensure_initialized!
|
201
|
+
|
202
|
+
if name.nil?
|
203
|
+
projects = @project_manager.list_projects
|
204
|
+
if projects.empty?
|
205
|
+
@ui.empty_state("No projects configured")
|
206
|
+
return
|
207
|
+
end
|
208
|
+
|
209
|
+
choices = projects.map { |p| { name: "#{p[:name]} (#{p[:type]})", value: p[:name] } }
|
210
|
+
name = @prompt.select("Select project:", choices)
|
211
|
+
end
|
212
|
+
|
213
|
+
begin
|
214
|
+
project = @project_manager.get_project(name)
|
215
|
+
raise Sxn::ProjectNotFoundError, "Project '#{name}' not found" unless project
|
216
|
+
|
217
|
+
@ui.section("Project Information: #{name}")
|
218
|
+
display_project_info(project, detailed: true)
|
219
|
+
|
220
|
+
# Show rules
|
221
|
+
rules_manager = Sxn::Core::RulesManager.new(@config_manager, @project_manager)
|
222
|
+
begin
|
223
|
+
rules = rules_manager.list_rules(name)
|
224
|
+
if rules.any?
|
225
|
+
@ui.subsection("Rules")
|
226
|
+
@table.rules(rules, name)
|
227
|
+
else
|
228
|
+
@ui.info("No rules configured for this project")
|
229
|
+
end
|
230
|
+
rescue StandardError => e
|
231
|
+
@ui.debug("Could not load rules: #{e.message}")
|
232
|
+
end
|
233
|
+
rescue Sxn::Error => e
|
234
|
+
@ui.error(e.message)
|
235
|
+
exit(e.exit_code)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
def ensure_initialized!
|
242
|
+
return if @config_manager.initialized?
|
243
|
+
|
244
|
+
@ui.error("Project not initialized")
|
245
|
+
@ui.recovery_suggestion("Run 'sxn init' to initialize sxn in this project")
|
246
|
+
exit(1)
|
247
|
+
end
|
248
|
+
|
249
|
+
def display_project_info(project, detailed: false)
|
250
|
+
@ui.newline
|
251
|
+
@ui.key_value("Name", project[:name])
|
252
|
+
@ui.key_value("Type", project[:type] || "unknown")
|
253
|
+
@ui.key_value("Path", project[:path])
|
254
|
+
@ui.key_value("Default Branch", project[:default_branch] || "master")
|
255
|
+
|
256
|
+
if detailed
|
257
|
+
# Additional validation info
|
258
|
+
validation = @project_manager.validate_project(project[:name])
|
259
|
+
status = validation[:valid] ? "✅ Valid" : "❌ Invalid"
|
260
|
+
@ui.key_value("Status", status)
|
261
|
+
|
262
|
+
unless validation[:valid]
|
263
|
+
@ui.subsection("Issues")
|
264
|
+
validation[:issues].each { |issue| @ui.list_item(issue) }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
@ui.newline
|
269
|
+
display_project_commands(project[:name])
|
270
|
+
end
|
271
|
+
|
272
|
+
def display_project_commands(project_name)
|
273
|
+
@ui.subsection("Available Commands")
|
274
|
+
|
275
|
+
@ui.command_example(
|
276
|
+
"sxn worktree add #{project_name} [branch]",
|
277
|
+
"Create worktree for this project"
|
278
|
+
)
|
279
|
+
|
280
|
+
@ui.command_example(
|
281
|
+
"sxn rules add #{project_name} <type> <config>",
|
282
|
+
"Add setup rules for this project"
|
283
|
+
)
|
284
|
+
|
285
|
+
@ui.command_example(
|
286
|
+
"sxn projects validate #{project_name}",
|
287
|
+
"Validate project configuration"
|
288
|
+
)
|
289
|
+
end
|
290
|
+
|
291
|
+
def display_detected_projects(projects)
|
292
|
+
projects.each do |project|
|
293
|
+
@ui.list_item("#{project[:name]} (#{project[:type]})", project[:path])
|
294
|
+
end
|
295
|
+
@ui.newline
|
296
|
+
@ui.info("Total: #{projects.size} projects detected")
|
297
|
+
end
|
298
|
+
|
299
|
+
def register_projects(projects)
|
300
|
+
return if projects.empty?
|
301
|
+
|
302
|
+
@ui.subsection("Registering Projects")
|
303
|
+
|
304
|
+
results = @project_manager.auto_register_projects(projects)
|
305
|
+
|
306
|
+
success_count = results.count { |r| r[:status] == :success }
|
307
|
+
error_count = results.count { |r| r[:status] == :error }
|
308
|
+
|
309
|
+
results.each do |result|
|
310
|
+
if result[:status] == :success
|
311
|
+
@ui.success("✅ #{result[:project][:name]}")
|
312
|
+
else
|
313
|
+
@ui.error("❌ #{result[:project][:name]}: #{result[:error]}")
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
@ui.newline
|
318
|
+
@ui.info("Registered #{success_count} projects successfully")
|
319
|
+
@ui.warning("#{error_count} projects failed") if error_count.positive?
|
320
|
+
end
|
321
|
+
|
322
|
+
def list_with_validation(projects)
|
323
|
+
@ui.subsection("Project Validation")
|
324
|
+
|
325
|
+
Sxn::UI::ProgressBar.with_progress("Validating projects", projects) do |project, progress|
|
326
|
+
validation = @project_manager.validate_project(project[:name])
|
327
|
+
|
328
|
+
status = validation[:valid] ? "✅" : "❌"
|
329
|
+
progress.log("#{status} #{project[:name]}")
|
330
|
+
|
331
|
+
unless validation[:valid]
|
332
|
+
validation[:issues].each do |issue|
|
333
|
+
progress.log(" - #{issue}")
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
validation
|
338
|
+
end
|
339
|
+
|
340
|
+
@ui.newline
|
341
|
+
@table.projects(projects)
|
342
|
+
end
|
343
|
+
|
344
|
+
def suggest_add_project
|
345
|
+
@ui.newline
|
346
|
+
@ui.recovery_suggestion("Add projects with 'sxn projects add <name> <path>' or scan with 'sxn projects scan'")
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|