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,871 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "yaml"
|
5
|
+
require "pathname"
|
6
|
+
|
7
|
+
module Sxn
|
8
|
+
module Rules
|
9
|
+
# ProjectDetector analyzes project directories to determine their type, language,
|
10
|
+
# package manager, and suggests appropriate default rules for project setup.
|
11
|
+
#
|
12
|
+
# @example Basic usage
|
13
|
+
# detector = ProjectDetector.new("/path/to/project")
|
14
|
+
# info = detector.detect_project_info
|
15
|
+
# puts "Project type: #{info[:type]}"
|
16
|
+
# puts "Package manager: #{info[:package_manager]}"
|
17
|
+
#
|
18
|
+
# rules = detector.suggest_default_rules
|
19
|
+
# puts "Suggested rules: #{rules.keys}"
|
20
|
+
#
|
21
|
+
class ProjectDetector
|
22
|
+
# Project type definitions with their detection criteria
|
23
|
+
PROJECT_TYPES = {
|
24
|
+
rails: {
|
25
|
+
files: %w[Gemfile config/application.rb],
|
26
|
+
patterns: {
|
27
|
+
gemfile_contains: ["rails"]
|
28
|
+
},
|
29
|
+
confidence: :high
|
30
|
+
},
|
31
|
+
ruby: {
|
32
|
+
files: %w[Gemfile *.gemspec],
|
33
|
+
patterns: {},
|
34
|
+
confidence: :medium
|
35
|
+
},
|
36
|
+
nextjs: {
|
37
|
+
files: %w[package.json next.config.js],
|
38
|
+
patterns: {
|
39
|
+
package_json_deps: ["next"]
|
40
|
+
},
|
41
|
+
confidence: :high
|
42
|
+
},
|
43
|
+
react: {
|
44
|
+
files: %w[package.json],
|
45
|
+
patterns: {
|
46
|
+
package_json_deps: ["react"]
|
47
|
+
},
|
48
|
+
confidence: :high
|
49
|
+
},
|
50
|
+
nodejs: {
|
51
|
+
files: %w[package.json],
|
52
|
+
patterns: {
|
53
|
+
package_json_deps: ["express", "fastify", "koa", "@types/node", "nodemon", "typescript"]
|
54
|
+
},
|
55
|
+
confidence: :medium_high
|
56
|
+
},
|
57
|
+
javascript: {
|
58
|
+
files: %w[package.json],
|
59
|
+
patterns: {},
|
60
|
+
confidence: :medium
|
61
|
+
},
|
62
|
+
typescript: {
|
63
|
+
files: %w[tsconfig.json *.ts],
|
64
|
+
patterns: {},
|
65
|
+
confidence: :high
|
66
|
+
},
|
67
|
+
python: {
|
68
|
+
files: %w[requirements.txt setup.py pyproject.toml Pipfile],
|
69
|
+
patterns: {},
|
70
|
+
confidence: :medium
|
71
|
+
},
|
72
|
+
django: {
|
73
|
+
files: %w[manage.py],
|
74
|
+
patterns: {
|
75
|
+
requirements_contains: ["django"]
|
76
|
+
},
|
77
|
+
confidence: :high
|
78
|
+
},
|
79
|
+
go: {
|
80
|
+
files: %w[go.mod go.sum *.go],
|
81
|
+
patterns: {},
|
82
|
+
confidence: :high
|
83
|
+
},
|
84
|
+
rust: {
|
85
|
+
files: %w[Cargo.toml Cargo.lock],
|
86
|
+
patterns: {},
|
87
|
+
confidence: :high
|
88
|
+
}
|
89
|
+
}.freeze
|
90
|
+
|
91
|
+
# Package manager detection patterns
|
92
|
+
PACKAGE_MANAGERS = {
|
93
|
+
bundler: {
|
94
|
+
files: %w[Gemfile Gemfile.lock],
|
95
|
+
command: "bundle"
|
96
|
+
},
|
97
|
+
npm: {
|
98
|
+
files: %w[package-lock.json],
|
99
|
+
command: "npm"
|
100
|
+
},
|
101
|
+
yarn: {
|
102
|
+
files: %w[yarn.lock],
|
103
|
+
command: "yarn"
|
104
|
+
},
|
105
|
+
pnpm: {
|
106
|
+
files: %w[pnpm-lock.yaml],
|
107
|
+
command: "pnpm"
|
108
|
+
},
|
109
|
+
pip: {
|
110
|
+
files: %w[requirements.txt],
|
111
|
+
command: "pip"
|
112
|
+
},
|
113
|
+
pipenv: {
|
114
|
+
files: %w[Pipfile Pipfile.lock],
|
115
|
+
command: "pipenv"
|
116
|
+
},
|
117
|
+
poetry: {
|
118
|
+
files: %w[pyproject.toml poetry.lock],
|
119
|
+
command: "poetry"
|
120
|
+
},
|
121
|
+
cargo: {
|
122
|
+
files: %w[Cargo.toml Cargo.lock],
|
123
|
+
command: "cargo"
|
124
|
+
},
|
125
|
+
go_mod: {
|
126
|
+
files: %w[go.mod go.sum],
|
127
|
+
command: "go"
|
128
|
+
}
|
129
|
+
}.freeze
|
130
|
+
|
131
|
+
attr_reader :project_path
|
132
|
+
|
133
|
+
# Initialize the project detector
|
134
|
+
#
|
135
|
+
# @param project_path [String] Absolute path to the project directory
|
136
|
+
def initialize(project_path)
|
137
|
+
raise ArgumentError, "Project path cannot be nil or empty" if project_path.nil? || project_path.empty?
|
138
|
+
|
139
|
+
@project_path = File.realpath(project_path)
|
140
|
+
validate_project_path!
|
141
|
+
rescue Errno::ENOENT
|
142
|
+
raise ArgumentError, "Project path does not exist: #{project_path}"
|
143
|
+
end
|
144
|
+
|
145
|
+
# Detect comprehensive project information
|
146
|
+
#
|
147
|
+
# @return [Hash] Project information including type, language, package manager, etc.
|
148
|
+
def detect_project_info
|
149
|
+
{
|
150
|
+
type: detect_project_type,
|
151
|
+
language: detect_primary_language,
|
152
|
+
languages: detect_all_languages,
|
153
|
+
package_manager: detect_package_manager,
|
154
|
+
framework: detect_framework,
|
155
|
+
has_docker: has_docker?,
|
156
|
+
has_tests: has_tests?,
|
157
|
+
has_ci: has_ci_config?,
|
158
|
+
database: detect_database,
|
159
|
+
sensitive_files: detect_sensitive_files,
|
160
|
+
analysis_timestamp: Time.now.iso8601
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
# Detect project type for a given path (used by ConfigManager)
|
165
|
+
#
|
166
|
+
# @param path [String] Path to the project directory
|
167
|
+
# @return [Symbol] Detected project type (:rails, :nodejs, :python, etc.)
|
168
|
+
def detect_type(path)
|
169
|
+
old_path = @project_path
|
170
|
+
@project_path = File.realpath(path)
|
171
|
+
result = detect_project_type
|
172
|
+
@project_path = old_path
|
173
|
+
result
|
174
|
+
rescue Errno::ENOENT
|
175
|
+
:unknown
|
176
|
+
end
|
177
|
+
|
178
|
+
# Legacy method for compatibility with tests
|
179
|
+
# Detect the primary project type
|
180
|
+
#
|
181
|
+
# @return [Symbol] Detected project type (:rails, :nodejs, :python, etc.)
|
182
|
+
def detect_project_type
|
183
|
+
detected_types = []
|
184
|
+
|
185
|
+
PROJECT_TYPES.each do |type, criteria|
|
186
|
+
confidence = calculate_type_confidence(type, criteria)
|
187
|
+
detected_types << { type: type, confidence: confidence } if confidence.positive?
|
188
|
+
end
|
189
|
+
|
190
|
+
# Sort by confidence and return the highest
|
191
|
+
detected_types.min_by { |t| -t[:confidence] }&.fetch(:type) || :unknown
|
192
|
+
end
|
193
|
+
|
194
|
+
# Detect the package manager used by the project
|
195
|
+
#
|
196
|
+
# @return [Symbol] Detected package manager (:bundler, :npm, :yarn, etc.)
|
197
|
+
def detect_package_manager
|
198
|
+
PACKAGE_MANAGERS.each do |manager, criteria|
|
199
|
+
return manager if criteria[:files].any? { |file| file_exists_in_project?(file) }
|
200
|
+
end
|
201
|
+
|
202
|
+
# Fallback logic for common scenarios
|
203
|
+
if file_exists_in_project?("package.json")
|
204
|
+
return :npm # Default to npm for Node.js projects without specific lock files
|
205
|
+
end
|
206
|
+
|
207
|
+
if file_exists_in_project?("Gemfile")
|
208
|
+
return :bundler # Default to bundler for Ruby projects without lock files
|
209
|
+
end
|
210
|
+
|
211
|
+
:unknown
|
212
|
+
end
|
213
|
+
|
214
|
+
# Suggest default rules based on detected project characteristics
|
215
|
+
#
|
216
|
+
# @return [Hash] Suggested rules configuration
|
217
|
+
def suggest_default_rules
|
218
|
+
project_info = detect_project_info
|
219
|
+
rules = {}
|
220
|
+
|
221
|
+
# Add copy files rules based on project type
|
222
|
+
copy_files = suggest_copy_files_rules(project_info)
|
223
|
+
rules["copy_files"] = copy_files unless copy_files["config"]["files"] && copy_files["config"]["files"].empty?
|
224
|
+
|
225
|
+
# Add setup commands rules based on package manager
|
226
|
+
setup_commands = suggest_setup_commands_rules(project_info)
|
227
|
+
unless setup_commands["config"]["commands"] && setup_commands["config"]["commands"].empty?
|
228
|
+
rules["setup_commands"] =
|
229
|
+
setup_commands
|
230
|
+
end
|
231
|
+
|
232
|
+
# Add template rules for common project documentation
|
233
|
+
template_rules = suggest_template_rules(project_info)
|
234
|
+
unless template_rules["config"]["templates"] && template_rules["config"]["templates"].empty?
|
235
|
+
rules["templates"] =
|
236
|
+
template_rules
|
237
|
+
end
|
238
|
+
|
239
|
+
rules
|
240
|
+
end
|
241
|
+
|
242
|
+
# Get detailed analysis of the project structure
|
243
|
+
#
|
244
|
+
# @return [Hash] Detailed project analysis
|
245
|
+
def analyze_project_structure
|
246
|
+
{
|
247
|
+
files: analyze_important_files,
|
248
|
+
directories: analyze_directory_structure,
|
249
|
+
dependencies: analyze_dependencies,
|
250
|
+
configuration: analyze_configuration_files,
|
251
|
+
scripts: analyze_scripts,
|
252
|
+
documentation: analyze_documentation
|
253
|
+
}
|
254
|
+
end
|
255
|
+
|
256
|
+
private
|
257
|
+
|
258
|
+
# Validate that the project path exists and is a directory
|
259
|
+
def validate_project_path!
|
260
|
+
raise ArgumentError, "Project path is not a directory: #{@project_path}" unless File.directory?(@project_path)
|
261
|
+
|
262
|
+
return if File.readable?(@project_path)
|
263
|
+
|
264
|
+
raise ArgumentError, "Project path is not readable: #{@project_path}"
|
265
|
+
end
|
266
|
+
|
267
|
+
# Calculate confidence score for a project type
|
268
|
+
def calculate_type_confidence(type, criteria)
|
269
|
+
confidence = 0
|
270
|
+
|
271
|
+
# Check for required files
|
272
|
+
files_found = criteria[:files].count { |file| file_exists_in_project?(file) }
|
273
|
+
if files_found.positive?
|
274
|
+
confidence += files_found * 10
|
275
|
+
confidence += 20 if files_found == criteria[:files].length
|
276
|
+
end
|
277
|
+
|
278
|
+
# For high-confidence project types with specific patterns,
|
279
|
+
# require all files AND pattern matches to be valid
|
280
|
+
if criteria[:confidence] == :high && !criteria[:patterns].empty?
|
281
|
+
return 0 unless files_found == criteria[:files].length
|
282
|
+
|
283
|
+
# All patterns must match for high-confidence types
|
284
|
+
pattern_matches = 0
|
285
|
+
criteria[:patterns].each do |pattern_type, patterns|
|
286
|
+
case pattern_type
|
287
|
+
when :gemfile_contains
|
288
|
+
pattern_matches += 1 if patterns.any? { |pattern| gemfile_contains?(pattern) }
|
289
|
+
when :package_json_deps
|
290
|
+
pattern_matches += 1 if patterns.any? { |dep| package_json_has_dependency?(dep) }
|
291
|
+
when :requirements_contains
|
292
|
+
pattern_matches += 1 if patterns.any? { |pattern| requirements_contains?(pattern) }
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
return 0 unless pattern_matches == criteria[:patterns].length
|
297
|
+
|
298
|
+
confidence += pattern_matches * 30
|
299
|
+
else
|
300
|
+
# For other types, add confidence for pattern matches
|
301
|
+
criteria[:patterns].each do |pattern_type, patterns|
|
302
|
+
case pattern_type
|
303
|
+
when :gemfile_contains
|
304
|
+
confidence += 30 if patterns.any? { |pattern| gemfile_contains?(pattern) }
|
305
|
+
when :package_json_deps
|
306
|
+
confidence += 30 if patterns.any? { |dep| package_json_has_dependency?(dep) }
|
307
|
+
when :requirements_contains
|
308
|
+
confidence += 30 if patterns.any? { |pattern| requirements_contains?(pattern) }
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
# Special logic for Node.js vs JavaScript distinction
|
314
|
+
# Only apply when using actual PROJECT_TYPES criteria for nodejs
|
315
|
+
# Don't boost Node.js confidence if this looks like a TypeScript project
|
316
|
+
if type == :nodejs && file_exists_in_project?("package.json") &&
|
317
|
+
criteria == PROJECT_TYPES[:nodejs] &&
|
318
|
+
!(file_exists_in_project?("tsconfig.json") && file_exists_in_project?("*.ts")) &&
|
319
|
+
has_nodejs_characteristics?
|
320
|
+
# Only boost Node.js if it has typical Node.js characteristics
|
321
|
+
# Otherwise treat it as plain JavaScript
|
322
|
+
confidence += 50
|
323
|
+
end
|
324
|
+
|
325
|
+
# Apply confidence modifiers
|
326
|
+
case criteria[:confidence]
|
327
|
+
when :high
|
328
|
+
confidence *= 1.2
|
329
|
+
when :medium_high
|
330
|
+
confidence *= 1.1
|
331
|
+
when :low
|
332
|
+
confidence *= 0.8
|
333
|
+
end
|
334
|
+
|
335
|
+
confidence.to_i
|
336
|
+
end
|
337
|
+
|
338
|
+
# Calculate confidence score for a specific project type (test compatibility)
|
339
|
+
def calculate_confidence_score(type)
|
340
|
+
criteria = PROJECT_TYPES[type]
|
341
|
+
return 0 unless criteria
|
342
|
+
|
343
|
+
calculate_type_confidence(type, criteria)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Check if a file exists in the project (supports glob patterns)
|
347
|
+
def file_exists_in_project?(file_pattern)
|
348
|
+
return false unless @project_path && File.directory?(@project_path)
|
349
|
+
|
350
|
+
if file_pattern.include?("*")
|
351
|
+
!Dir.glob(File.join(@project_path, file_pattern)).empty?
|
352
|
+
else
|
353
|
+
File.exist?(File.join(@project_path, file_pattern))
|
354
|
+
end
|
355
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
356
|
+
# Handle permission errors and I/O errors gracefully
|
357
|
+
false
|
358
|
+
end
|
359
|
+
|
360
|
+
# Detect primary programming language
|
361
|
+
def detect_primary_language
|
362
|
+
return :unknown unless @project_path && File.directory?(@project_path)
|
363
|
+
|
364
|
+
language_files = {
|
365
|
+
ruby: %w[*.rb Gemfile Rakefile],
|
366
|
+
javascript: %w[*.js *.jsx package.json],
|
367
|
+
typescript: %w[*.ts *.tsx tsconfig.json],
|
368
|
+
python: %w[*.py requirements.txt setup.py],
|
369
|
+
go: %w[*.go go.mod],
|
370
|
+
rust: %w[*.rs Cargo.toml],
|
371
|
+
java: %w[*.java pom.xml build.gradle],
|
372
|
+
php: %w[*.php composer.json],
|
373
|
+
csharp: %w[*.cs *.csproj],
|
374
|
+
cpp: %w[*.cpp *.hpp *.cmake CMakeLists.txt]
|
375
|
+
}
|
376
|
+
|
377
|
+
language_scores = {}
|
378
|
+
|
379
|
+
language_files.each do |language, patterns|
|
380
|
+
score = patterns.sum do |pattern|
|
381
|
+
if pattern.include?("*")
|
382
|
+
Dir.glob(File.join(@project_path, "**", pattern)).length
|
383
|
+
else
|
384
|
+
file_exists_in_project?(pattern) ? 10 : 0
|
385
|
+
end
|
386
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
387
|
+
0
|
388
|
+
end
|
389
|
+
language_scores[language] = score
|
390
|
+
end
|
391
|
+
|
392
|
+
language_scores.max_by { |_, score| score }&.first || :unknown
|
393
|
+
rescue StandardError
|
394
|
+
:unknown
|
395
|
+
end
|
396
|
+
|
397
|
+
# Detect all languages present in the project
|
398
|
+
def detect_all_languages
|
399
|
+
return [] unless @project_path && File.directory?(@project_path)
|
400
|
+
|
401
|
+
language_files = {
|
402
|
+
ruby: %w[*.rb Gemfile Rakefile],
|
403
|
+
javascript: %w[*.js *.jsx package.json],
|
404
|
+
typescript: %w[*.ts *.tsx tsconfig.json],
|
405
|
+
python: %w[*.py requirements.txt setup.py],
|
406
|
+
go: %w[*.go go.mod],
|
407
|
+
rust: %w[*.rs Cargo.toml],
|
408
|
+
java: %w[*.java pom.xml build.gradle],
|
409
|
+
php: %w[*.php composer.json],
|
410
|
+
csharp: %w[*.cs *.csproj],
|
411
|
+
cpp: %w[*.cpp *.hpp *.cmake CMakeLists.txt]
|
412
|
+
}
|
413
|
+
|
414
|
+
detected_languages = []
|
415
|
+
language_files.each do |language, patterns|
|
416
|
+
score = patterns.sum do |pattern|
|
417
|
+
if pattern.include?("*")
|
418
|
+
Dir.glob(File.join(@project_path, "**", pattern)).length
|
419
|
+
else
|
420
|
+
file_exists_in_project?(pattern) ? 1 : 0
|
421
|
+
end
|
422
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
423
|
+
0
|
424
|
+
end
|
425
|
+
detected_languages << language if score.positive?
|
426
|
+
end
|
427
|
+
|
428
|
+
detected_languages
|
429
|
+
rescue StandardError
|
430
|
+
[]
|
431
|
+
end
|
432
|
+
|
433
|
+
# Detect web framework
|
434
|
+
def detect_framework
|
435
|
+
return :rails if gemfile_contains?("rails")
|
436
|
+
return :django if requirements_contains?("django")
|
437
|
+
return :nextjs if package_json_has_dependency?("next")
|
438
|
+
return :react if package_json_has_dependency?("react")
|
439
|
+
return :vue if package_json_has_dependency?("vue")
|
440
|
+
return :express if package_json_has_dependency?("express")
|
441
|
+
return :fastapi if requirements_contains?("fastapi")
|
442
|
+
return :flask if requirements_contains?("flask")
|
443
|
+
|
444
|
+
:unknown
|
445
|
+
end
|
446
|
+
|
447
|
+
# Check if project has Docker configuration
|
448
|
+
def has_docker?
|
449
|
+
file_exists_in_project?("Dockerfile") ||
|
450
|
+
file_exists_in_project?("docker-compose.yml") ||
|
451
|
+
file_exists_in_project?("docker-compose.yaml")
|
452
|
+
end
|
453
|
+
|
454
|
+
# Check if project has test configuration
|
455
|
+
def has_tests?
|
456
|
+
test_patterns = %w[
|
457
|
+
spec test tests __tests__ *.test.* *.spec.*
|
458
|
+
pytest.ini tox.ini jest.config.* vitest.config.*
|
459
|
+
]
|
460
|
+
|
461
|
+
test_patterns.any? { |pattern| file_exists_in_project?(pattern) }
|
462
|
+
end
|
463
|
+
|
464
|
+
# Check if project has CI configuration
|
465
|
+
def has_ci_config?
|
466
|
+
ci_files = %w[
|
467
|
+
.github/workflows .gitlab-ci.yml .circleci/config.yml
|
468
|
+
.travis.yml appveyor.yml .buildkite
|
469
|
+
]
|
470
|
+
|
471
|
+
ci_files.any? { |file| file_exists_in_project?(file) }
|
472
|
+
end
|
473
|
+
|
474
|
+
# Detect database configuration
|
475
|
+
def detect_database
|
476
|
+
databases = []
|
477
|
+
|
478
|
+
# Check configuration files and environment files
|
479
|
+
databases << :postgresql if file_contains?("config/database.yml",
|
480
|
+
"postgresql") || env_contains?("DATABASE_URL",
|
481
|
+
"postgres") || file_contains?(".env",
|
482
|
+
"postgresql://")
|
483
|
+
databases << :mysql if file_contains?("config/database.yml",
|
484
|
+
"mysql") || env_contains?("DATABASE_URL",
|
485
|
+
"mysql") || file_contains?(".env", "mysql://")
|
486
|
+
databases << :sqlite if file_contains?("config/database.yml", "sqlite") || file_exists_in_project?("*.sqlite*")
|
487
|
+
databases << :mongodb if package_json_has_dependency?("mongoose") || requirements_contains?("pymongo")
|
488
|
+
databases << :redis if package_json_has_dependency?("redis") || requirements_contains?("redis")
|
489
|
+
|
490
|
+
databases.first || :unknown
|
491
|
+
end
|
492
|
+
|
493
|
+
# Detect sensitive files that should be handled carefully
|
494
|
+
def detect_sensitive_files
|
495
|
+
sensitive_patterns = %w[
|
496
|
+
config/master.key config/credentials/* .env .env.*
|
497
|
+
*.pem *.p12 *.jks .npmrc auth_token api_key
|
498
|
+
]
|
499
|
+
|
500
|
+
found_files = []
|
501
|
+
sensitive_patterns.each do |pattern|
|
502
|
+
if pattern.include?("*")
|
503
|
+
found_files.concat(Dir.glob(File.join(@project_path, "**", pattern)))
|
504
|
+
else
|
505
|
+
file_path = File.join(@project_path, pattern)
|
506
|
+
found_files << file_path if File.exist?(file_path)
|
507
|
+
end
|
508
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
509
|
+
# Skip patterns that cause errors
|
510
|
+
end
|
511
|
+
|
512
|
+
found_files.map { |f| Pathname.new(f).relative_path_from(Pathname.new(@project_path)).to_s }
|
513
|
+
end
|
514
|
+
|
515
|
+
# Suggest copy files rules based on project characteristics
|
516
|
+
def suggest_copy_files_rules(project_info)
|
517
|
+
files = []
|
518
|
+
|
519
|
+
case project_info[:type]
|
520
|
+
when :rails
|
521
|
+
files.push(
|
522
|
+
{ "source" => "config/master.key", "strategy" => "copy", "required" => false },
|
523
|
+
{ "source" => ".env", "strategy" => "symlink", "required" => false },
|
524
|
+
{ "source" => ".env.development", "strategy" => "symlink", "required" => false }
|
525
|
+
)
|
526
|
+
when :nodejs, :nextjs, :react
|
527
|
+
files.push(
|
528
|
+
{ "source" => ".env", "strategy" => "symlink", "required" => false },
|
529
|
+
{ "source" => ".env.local", "strategy" => "symlink", "required" => false },
|
530
|
+
{ "source" => ".npmrc", "strategy" => "copy", "required" => false }
|
531
|
+
)
|
532
|
+
when :python, :django
|
533
|
+
files.push(
|
534
|
+
{ "source" => ".env", "strategy" => "symlink", "required" => false },
|
535
|
+
{ "source" => "secrets.yml", "strategy" => "copy", "required" => false }
|
536
|
+
)
|
537
|
+
end
|
538
|
+
|
539
|
+
# Add any detected sensitive files
|
540
|
+
project_info[:sensitive_files].each do |file|
|
541
|
+
next if files.any? { |f| f["source"] == file }
|
542
|
+
|
543
|
+
strategy = file.match?(/\.(key|pem|p12|jks)$/) ? "copy" : "symlink"
|
544
|
+
files << { "source" => file, "strategy" => strategy, "required" => false }
|
545
|
+
end
|
546
|
+
|
547
|
+
{ "type" => "copy_files", "config" => { "files" => files } }
|
548
|
+
end
|
549
|
+
|
550
|
+
# Suggest setup commands based on package manager
|
551
|
+
def suggest_setup_commands_rules(project_info)
|
552
|
+
commands = []
|
553
|
+
|
554
|
+
case project_info[:package_manager]
|
555
|
+
when :bundler
|
556
|
+
commands << { "command" => %w[bundle install], "description" => "Install Ruby dependencies" }
|
557
|
+
if project_info[:type] == :rails
|
558
|
+
commands << { "command" => ["bin/rails", "db:create"],
|
559
|
+
"condition" => "file_missing:db/development.sqlite3", "description" => "Create database" }
|
560
|
+
commands << { "command" => ["bin/rails", "db:migrate"], "description" => "Run database migrations" }
|
561
|
+
end
|
562
|
+
when :npm
|
563
|
+
commands << { "command" => %w[npm install], "description" => "Install Node.js dependencies" }
|
564
|
+
commands << { "command" => %w[npm run build], "condition" => "file_exists:package.json",
|
565
|
+
"required" => false, "description" => "Build project" }
|
566
|
+
when :yarn
|
567
|
+
commands << { "command" => %w[yarn install], "description" => "Install Node.js dependencies" }
|
568
|
+
commands << { "command" => %w[yarn build], "condition" => "file_exists:package.json", "required" => false,
|
569
|
+
"description" => "Build project" }
|
570
|
+
when :pnpm
|
571
|
+
commands << { "command" => %w[pnpm install], "description" => "Install Node.js dependencies" }
|
572
|
+
when :pip
|
573
|
+
commands << { "command" => ["pip", "install", "-r", "requirements.txt"],
|
574
|
+
"description" => "Install Python dependencies" }
|
575
|
+
when :pipenv
|
576
|
+
commands << { "command" => %w[pipenv install], "description" => "Install Python dependencies" }
|
577
|
+
when :poetry
|
578
|
+
commands << { "command" => %w[poetry install], "description" => "Install Python dependencies" }
|
579
|
+
end
|
580
|
+
|
581
|
+
{ "type" => "setup_commands", "config" => { "commands" => commands } }
|
582
|
+
end
|
583
|
+
|
584
|
+
# Suggest template rules for documentation
|
585
|
+
def suggest_template_rules(project_info)
|
586
|
+
templates = []
|
587
|
+
|
588
|
+
# Always suggest session info template
|
589
|
+
templates << {
|
590
|
+
"source" => ".sxn/templates/session-info.md.liquid",
|
591
|
+
"destination" => "SESSION_INFO.md",
|
592
|
+
"required" => false
|
593
|
+
}
|
594
|
+
|
595
|
+
# Language-specific templates
|
596
|
+
case project_info[:type]
|
597
|
+
when :rails
|
598
|
+
templates << {
|
599
|
+
"source" => ".sxn/templates/rails/CLAUDE.md.liquid",
|
600
|
+
"destination" => "CLAUDE.md",
|
601
|
+
"required" => false
|
602
|
+
}
|
603
|
+
when :nodejs, :nextjs, :react
|
604
|
+
templates << {
|
605
|
+
"source" => ".sxn/templates/javascript/README.md.liquid",
|
606
|
+
"destination" => "README.md",
|
607
|
+
"required" => false,
|
608
|
+
"overwrite" => false
|
609
|
+
}
|
610
|
+
end
|
611
|
+
|
612
|
+
{ "type" => "template", "config" => { "templates" => templates } }
|
613
|
+
end
|
614
|
+
|
615
|
+
# Check if project has typical Node.js characteristics
|
616
|
+
def has_nodejs_characteristics?
|
617
|
+
return false unless file_exists_in_project?("package.json")
|
618
|
+
|
619
|
+
# Check for Node.js-specific dependencies or scripts
|
620
|
+
nodejs_indicators = %w[
|
621
|
+
express fastify koa hapi
|
622
|
+
nodemon pm2 forever
|
623
|
+
@types/node typescript ts-node
|
624
|
+
eslint jest mocha nyc
|
625
|
+
webpack parcel rollup
|
626
|
+
commander inquirer chalk
|
627
|
+
axios request node-fetch
|
628
|
+
]
|
629
|
+
|
630
|
+
# Check for Node.js specific scripts
|
631
|
+
nodejs_scripts = %w[start dev server build test]
|
632
|
+
|
633
|
+
has_nodejs_deps = nodejs_indicators.any? { |dep| package_json_has_dependency?(dep) }
|
634
|
+
has_nodejs_scripts = nodejs_scripts.any? { |script| package_json_has_script?(script) }
|
635
|
+
has_main_entry = package_json_has_main_entry?
|
636
|
+
|
637
|
+
has_nodejs_deps || has_nodejs_scripts || has_main_entry
|
638
|
+
end
|
639
|
+
|
640
|
+
# Helper methods for file content checking
|
641
|
+
def gemfile_contains?(gem_name)
|
642
|
+
gemfile_path = File.join(@project_path, "Gemfile")
|
643
|
+
return false unless File.exist?(gemfile_path)
|
644
|
+
|
645
|
+
File.read(gemfile_path).include?(gem_name)
|
646
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
647
|
+
false
|
648
|
+
end
|
649
|
+
|
650
|
+
def package_json_has_dependency?(dep_name)
|
651
|
+
package_json_path = File.join(@project_path, "package.json")
|
652
|
+
return false unless File.exist?(package_json_path)
|
653
|
+
|
654
|
+
begin
|
655
|
+
package_data = JSON.parse(File.read(package_json_path))
|
656
|
+
all_deps = {}
|
657
|
+
all_deps.merge!(package_data["dependencies"] || {})
|
658
|
+
all_deps.merge!(package_data["devDependencies"] || {})
|
659
|
+
all_deps.merge!(package_data["peerDependencies"] || {})
|
660
|
+
|
661
|
+
all_deps.key?(dep_name)
|
662
|
+
rescue JSON::ParserError, Errno::EACCES, Errno::EIO, StandardError
|
663
|
+
false
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
def requirements_contains?(package_name)
|
668
|
+
requirements_path = File.join(@project_path, "requirements.txt")
|
669
|
+
return false unless File.exist?(requirements_path)
|
670
|
+
|
671
|
+
File.read(requirements_path).downcase.include?(package_name.downcase)
|
672
|
+
rescue Errno::EACCES, Errno::EIO, StandardError
|
673
|
+
false
|
674
|
+
end
|
675
|
+
|
676
|
+
def package_json_has_script?(script_name)
|
677
|
+
package_json_path = File.join(@project_path, "package.json")
|
678
|
+
return false unless File.exist?(package_json_path)
|
679
|
+
|
680
|
+
begin
|
681
|
+
package_data = JSON.parse(File.read(package_json_path))
|
682
|
+
scripts = package_data["scripts"] || {}
|
683
|
+
scripts.key?(script_name)
|
684
|
+
rescue JSON::ParserError
|
685
|
+
false
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
def package_json_has_main_entry?
|
690
|
+
package_json_path = File.join(@project_path, "package.json")
|
691
|
+
return false unless File.exist?(package_json_path)
|
692
|
+
|
693
|
+
begin
|
694
|
+
package_data = JSON.parse(File.read(package_json_path))
|
695
|
+
package_data.key?("main") || package_data.key?("module") || package_data.key?("exports")
|
696
|
+
rescue JSON::ParserError
|
697
|
+
false
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
def file_contains?(file_path, content)
|
702
|
+
full_path = File.join(@project_path, file_path)
|
703
|
+
return false unless File.exist?(full_path)
|
704
|
+
|
705
|
+
File.read(full_path).downcase.include?(content.downcase)
|
706
|
+
rescue StandardError
|
707
|
+
false
|
708
|
+
end
|
709
|
+
|
710
|
+
def env_contains?(env_var, content)
|
711
|
+
env_value = ENV.fetch(env_var, nil)
|
712
|
+
return false unless env_value
|
713
|
+
|
714
|
+
env_value.include?(content)
|
715
|
+
end
|
716
|
+
|
717
|
+
# Analysis methods for detailed project inspection
|
718
|
+
def analyze_important_files
|
719
|
+
important_patterns = %w[
|
720
|
+
README* LICENSE* CHANGELOG* CONTRIBUTING*
|
721
|
+
Dockerfile docker-compose.* .dockerignore
|
722
|
+
.gitignore .gitattributes
|
723
|
+
Makefile Rakefile
|
724
|
+
]
|
725
|
+
|
726
|
+
found_files = []
|
727
|
+
important_patterns.each do |pattern|
|
728
|
+
found_files.concat(Dir.glob(File.join(@project_path, pattern), File::FNM_CASEFOLD))
|
729
|
+
end
|
730
|
+
|
731
|
+
found_files.map { |f| File.basename(f) }
|
732
|
+
end
|
733
|
+
|
734
|
+
def analyze_directory_structure
|
735
|
+
important_dirs = %w[
|
736
|
+
src lib app bin config test spec tests
|
737
|
+
public assets static dist build
|
738
|
+
docs documentation
|
739
|
+
]
|
740
|
+
|
741
|
+
important_dirs.select do |dir|
|
742
|
+
File.directory?(File.join(@project_path, dir))
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
746
|
+
def analyze_dependencies
|
747
|
+
deps = {}
|
748
|
+
|
749
|
+
# Ruby dependencies
|
750
|
+
deps[:ruby] = parse_gemfile_lock if file_exists_in_project?("Gemfile.lock")
|
751
|
+
|
752
|
+
# Node.js dependencies
|
753
|
+
deps[:nodejs] = parse_package_json if file_exists_in_project?("package.json")
|
754
|
+
|
755
|
+
# Python dependencies
|
756
|
+
deps[:python] = parse_requirements_txt if file_exists_in_project?("requirements.txt")
|
757
|
+
|
758
|
+
deps
|
759
|
+
end
|
760
|
+
|
761
|
+
# Parse dependencies by type for testing
|
762
|
+
def parse_dependencies(type)
|
763
|
+
case type
|
764
|
+
when :bundler, :ruby
|
765
|
+
if file_exists_in_project?("Gemfile.lock")
|
766
|
+
parse_gemfile_lock
|
767
|
+
elsif file_exists_in_project?("Gemfile")
|
768
|
+
parse_gemfile
|
769
|
+
else
|
770
|
+
[]
|
771
|
+
end
|
772
|
+
when :npm, :nodejs
|
773
|
+
file_exists_in_project?("package.json") ? parse_package_json : []
|
774
|
+
when :python
|
775
|
+
file_exists_in_project?("requirements.txt") ? parse_requirements_txt : []
|
776
|
+
else
|
777
|
+
[]
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
def analyze_configuration_files
|
782
|
+
Dir.glob(File.join(@project_path, "**", "*.{yml,yaml,json,toml,ini,conf,config}"))
|
783
|
+
.map { |f| Pathname.new(f).relative_path_from(Pathname.new(@project_path)).to_s }
|
784
|
+
.select { |f| !f.start_with?("node_modules/") && !f.start_with?(".git/") }
|
785
|
+
end
|
786
|
+
|
787
|
+
def analyze_scripts
|
788
|
+
scripts = {}
|
789
|
+
|
790
|
+
# Package.json scripts
|
791
|
+
if file_exists_in_project?("package.json")
|
792
|
+
begin
|
793
|
+
package_data = JSON.parse(File.read(File.join(@project_path, "package.json")))
|
794
|
+
scripts[:npm] = package_data["scripts"]&.keys || []
|
795
|
+
rescue JSON::ParserError
|
796
|
+
# Ignore parsing errors
|
797
|
+
end
|
798
|
+
end
|
799
|
+
|
800
|
+
# Executable files
|
801
|
+
executable_files = Dir.glob(File.join(@project_path, "bin/*")).select { |f| File.executable?(f) }
|
802
|
+
scripts[:executables] = executable_files.map { |f| File.basename(f) }
|
803
|
+
|
804
|
+
scripts
|
805
|
+
end
|
806
|
+
|
807
|
+
def analyze_documentation
|
808
|
+
Dir.glob(File.join(@project_path, "**", "*.{md,txt,rst,adoc}"))
|
809
|
+
.map { |f| Pathname.new(f).relative_path_from(Pathname.new(@project_path)).to_s }
|
810
|
+
.select { |f| !f.start_with?("node_modules/") && !f.start_with?(".git/") }
|
811
|
+
end
|
812
|
+
|
813
|
+
# Dependency parsing helpers (simplified implementations)
|
814
|
+
def parse_gemfile_lock
|
815
|
+
# Simplified - would need more robust parsing for production
|
816
|
+
return [] unless file_exists_in_project?("Gemfile.lock")
|
817
|
+
|
818
|
+
begin
|
819
|
+
# Try to read and parse the file
|
820
|
+
content = File.read(File.join(@project_path, "Gemfile.lock"))
|
821
|
+
# Simplified parsing - just return a hardcoded list if content contains gem specs
|
822
|
+
content.include?("GEM") || content.include?("specs:") ? ["gems from Gemfile.lock"] : []
|
823
|
+
rescue StandardError
|
824
|
+
[]
|
825
|
+
end
|
826
|
+
end
|
827
|
+
|
828
|
+
def parse_gemfile
|
829
|
+
# Parse Gemfile for gem dependencies
|
830
|
+
return [] unless file_exists_in_project?("Gemfile")
|
831
|
+
|
832
|
+
begin
|
833
|
+
content = File.read(File.join(@project_path, "Gemfile"))
|
834
|
+
gems = []
|
835
|
+
|
836
|
+
# Extract gem names using regex
|
837
|
+
content.scan(/gem\s+['"]([^'"]+)['"]/) do |match|
|
838
|
+
gems << match[0]
|
839
|
+
end
|
840
|
+
|
841
|
+
gems
|
842
|
+
rescue StandardError
|
843
|
+
[]
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
def parse_package_json
|
848
|
+
return [] unless file_exists_in_project?("package.json")
|
849
|
+
|
850
|
+
begin
|
851
|
+
content = File.read(File.join(@project_path, "package.json"))
|
852
|
+
data = JSON.parse(content)
|
853
|
+
|
854
|
+
dependencies = []
|
855
|
+
dependencies.concat(data["dependencies"]&.keys || [])
|
856
|
+
dependencies.concat(data["devDependencies"]&.keys || [])
|
857
|
+
dependencies.concat(data["peerDependencies"]&.keys || [])
|
858
|
+
|
859
|
+
dependencies.uniq
|
860
|
+
rescue JSON::ParserError, StandardError
|
861
|
+
[]
|
862
|
+
end
|
863
|
+
end
|
864
|
+
|
865
|
+
def parse_requirements_txt
|
866
|
+
# Simplified - would need more robust parsing for production
|
867
|
+
["packages from requirements.txt"]
|
868
|
+
end
|
869
|
+
end
|
870
|
+
end
|
871
|
+
end
|