enhance_swarm 1.0.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
  3. data/.enhance_swarm/user_patterns.json +37 -0
  4. data/CHANGELOG.md +184 -0
  5. data/LICENSE +21 -0
  6. data/PRODUCTION_TEST_LOG.md +502 -0
  7. data/README.md +905 -0
  8. data/Rakefile +28 -0
  9. data/USAGE_EXAMPLES.md +477 -0
  10. data/examples/enhance_workflow.md +346 -0
  11. data/examples/rails_project.md +253 -0
  12. data/exe/enhance-swarm +30 -0
  13. data/lib/enhance_swarm/additional_commands.rb +299 -0
  14. data/lib/enhance_swarm/agent_communicator.rb +460 -0
  15. data/lib/enhance_swarm/agent_reviewer.rb +283 -0
  16. data/lib/enhance_swarm/agent_spawner.rb +462 -0
  17. data/lib/enhance_swarm/cleanup_manager.rb +245 -0
  18. data/lib/enhance_swarm/cli.rb +1592 -0
  19. data/lib/enhance_swarm/command_executor.rb +78 -0
  20. data/lib/enhance_swarm/configuration.rb +324 -0
  21. data/lib/enhance_swarm/control_agent.rb +307 -0
  22. data/lib/enhance_swarm/dependency_validator.rb +195 -0
  23. data/lib/enhance_swarm/error_recovery.rb +785 -0
  24. data/lib/enhance_swarm/generator.rb +194 -0
  25. data/lib/enhance_swarm/interrupt_handler.rb +512 -0
  26. data/lib/enhance_swarm/logger.rb +106 -0
  27. data/lib/enhance_swarm/mcp_integration.rb +85 -0
  28. data/lib/enhance_swarm/monitor.rb +28 -0
  29. data/lib/enhance_swarm/notification_manager.rb +444 -0
  30. data/lib/enhance_swarm/orchestrator.rb +313 -0
  31. data/lib/enhance_swarm/output_streamer.rb +281 -0
  32. data/lib/enhance_swarm/process_monitor.rb +266 -0
  33. data/lib/enhance_swarm/progress_tracker.rb +215 -0
  34. data/lib/enhance_swarm/project_analyzer.rb +612 -0
  35. data/lib/enhance_swarm/resource_manager.rb +177 -0
  36. data/lib/enhance_swarm/retry_handler.rb +40 -0
  37. data/lib/enhance_swarm/session_manager.rb +247 -0
  38. data/lib/enhance_swarm/signal_handler.rb +95 -0
  39. data/lib/enhance_swarm/smart_defaults.rb +708 -0
  40. data/lib/enhance_swarm/task_integration.rb +150 -0
  41. data/lib/enhance_swarm/task_manager.rb +174 -0
  42. data/lib/enhance_swarm/version.rb +5 -0
  43. data/lib/enhance_swarm/visual_dashboard.rb +555 -0
  44. data/lib/enhance_swarm/web_ui.rb +211 -0
  45. data/lib/enhance_swarm.rb +69 -0
  46. data/setup.sh +86 -0
  47. data/sig/enhance_swarm.rbs +4 -0
  48. data/templates/claude/CLAUDE.md +160 -0
  49. data/templates/claude/MCP.md +117 -0
  50. data/templates/claude/PERSONAS.md +114 -0
  51. data/templates/claude/RULES.md +221 -0
  52. data/test_builtin_functionality.rb +121 -0
  53. data/test_core_components.rb +156 -0
  54. data/test_real_claude_integration.rb +285 -0
  55. data/test_security.rb +150 -0
  56. data/test_smart_defaults.rb +155 -0
  57. data/test_task_integration.rb +173 -0
  58. data/test_web_ui.rb +245 -0
  59. data/web/assets/css/main.css +645 -0
  60. data/web/assets/js/kanban.js +499 -0
  61. data/web/assets/js/main.js +525 -0
  62. data/web/templates/dashboard.html.erb +226 -0
  63. data/web/templates/kanban.html.erb +193 -0
  64. metadata +293 -0
@@ -0,0 +1,612 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'json'
5
+ require 'yaml'
6
+ require_relative 'logger'
7
+
8
+ module EnhanceSwarm
9
+ class ProjectAnalyzer
10
+ attr_reader :project_root, :analysis_results
11
+
12
+ def initialize(project_root = Dir.pwd)
13
+ @project_root = Pathname.new(project_root)
14
+ @analysis_results = {}
15
+ end
16
+
17
+ def analyze
18
+ Logger.info("Analyzing project at: #{@project_root}")
19
+
20
+ @analysis_results = {
21
+ project_type: detect_project_type,
22
+ technology_stack: detect_technology_stack,
23
+ documentation: analyze_documentation,
24
+ testing_framework: detect_testing_framework,
25
+ build_system: detect_build_system,
26
+ deployment: detect_deployment_config,
27
+ database: detect_database,
28
+ frontend_framework: detect_frontend_framework,
29
+ project_structure: analyze_project_structure,
30
+ git_info: analyze_git_info,
31
+ package_managers: detect_package_managers,
32
+ ci_cd: detect_ci_cd,
33
+ containerization: detect_containerization,
34
+ recommended_agents: recommend_agents,
35
+ smart_commands: generate_smart_commands
36
+ }
37
+
38
+ Logger.info("Project analysis completed. Type: #{@analysis_results[:project_type]}")
39
+ @analysis_results
40
+ end
41
+
42
+ def generate_smart_defaults
43
+ return {} unless @analysis_results.any?
44
+
45
+ {
46
+ project_name: detect_project_name,
47
+ project_description: generate_project_description,
48
+ technology_stack: @analysis_results[:technology_stack],
49
+ test_command: @analysis_results[:smart_commands][:test],
50
+ lint_command: @analysis_results[:smart_commands][:lint],
51
+ build_command: @analysis_results[:smart_commands][:build],
52
+ start_command: @analysis_results[:smart_commands][:start],
53
+ max_concurrent_agents: recommend_agent_count,
54
+ preferred_agents: @analysis_results[:recommended_agents],
55
+ documentation_path: @analysis_results[:documentation][:primary_path],
56
+ has_documentation: @analysis_results[:documentation][:has_docs]
57
+ }
58
+ end
59
+
60
+ private
61
+
62
+ def detect_project_type
63
+ return 'rails' if rails_project?
64
+ return 'react' if react_project?
65
+ return 'vue' if vue_project?
66
+ return 'angular' if angular_project?
67
+ return 'nextjs' if nextjs_project?
68
+ return 'django' if django_project?
69
+ return 'flask' if flask_project?
70
+ return 'express' if express_project?
71
+ return 'ruby_gem' if ruby_gem?
72
+ return 'npm_package' if npm_package?
73
+ return 'python_package' if python_package?
74
+ return 'static_site' if static_site?
75
+ return 'monorepo' if monorepo?
76
+
77
+ 'unknown'
78
+ end
79
+
80
+ def detect_technology_stack
81
+ stack = []
82
+
83
+ # Languages
84
+ stack << 'Ruby' if has_file?('Gemfile') || has_files_with_extension?('.rb')
85
+ stack << 'JavaScript' if has_file?('package.json') || has_files_with_extension?('.js')
86
+ stack << 'TypeScript' if has_file?('tsconfig.json') || has_files_with_extension?('.ts')
87
+ stack << 'Python' if has_file?('requirements.txt') || has_file?('pyproject.toml') || has_files_with_extension?('.py')
88
+ stack << 'Go' if has_file?('go.mod') || has_files_with_extension?('.go')
89
+ stack << 'Rust' if has_file?('Cargo.toml')
90
+ stack << 'Java' if has_file?('pom.xml') || has_file?('build.gradle')
91
+ stack << 'PHP' if has_file?('composer.json') || has_files_with_extension?('.php')
92
+
93
+ # Frameworks
94
+ stack << 'Rails' if rails_project?
95
+ stack << 'React' if react_project?
96
+ stack << 'Vue.js' if vue_project?
97
+ stack << 'Angular' if angular_project?
98
+ stack << 'Next.js' if nextjs_project?
99
+ stack << 'Django' if django_project?
100
+ stack << 'Flask' if flask_project?
101
+ stack << 'Express' if express_project?
102
+
103
+ # Databases
104
+ stack.concat(@analysis_results[:database] || detect_database)
105
+
106
+ # Build tools
107
+ stack << 'Webpack' if has_file?('webpack.config.js')
108
+ stack << 'Vite' if has_file?('vite.config.js') || has_file?('vite.config.ts')
109
+ stack << 'Rollup' if has_file?('rollup.config.js')
110
+ stack << 'Parcel' if has_dependency?('parcel')
111
+
112
+ # CSS frameworks
113
+ stack << 'Tailwind CSS' if has_dependency?('tailwindcss') || has_file?('tailwind.config.js')
114
+ stack << 'Bootstrap' if has_dependency?('bootstrap')
115
+ stack << 'Sass' if has_files_with_extension?('.scss') || has_files_with_extension?('.sass')
116
+
117
+ stack.uniq.compact
118
+ end
119
+
120
+ def analyze_documentation
121
+ docs_info = {
122
+ has_docs: false,
123
+ primary_path: nil,
124
+ doc_types: [],
125
+ readme_quality: analyze_readme_quality
126
+ }
127
+
128
+ # Check for common documentation directories
129
+ doc_paths = ['docs', 'doc', 'documentation', 'wiki', '.github']
130
+ doc_paths.each do |path|
131
+ if directory_exists?(path)
132
+ docs_info[:has_docs] = true
133
+ docs_info[:primary_path] ||= path
134
+ docs_info[:doc_types] << path
135
+ end
136
+ end
137
+
138
+ # Check for specific documentation files
139
+ doc_files = ['API.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'DEPLOYMENT.md']
140
+ doc_files.each do |file|
141
+ if has_file?(file)
142
+ docs_info[:has_docs] = true
143
+ docs_info[:doc_types] << file.downcase.gsub('.md', '')
144
+ end
145
+ end
146
+
147
+ # Check for documentation generators
148
+ docs_info[:doc_types] << 'yard' if has_file?('.yardopts') || has_dependency?('yard')
149
+ docs_info[:doc_types] << 'sphinx' if has_file?('conf.py') && has_files_with_extension?('.rst')
150
+ docs_info[:doc_types] << 'jsdoc' if has_dependency?('jsdoc')
151
+ docs_info[:doc_types] << 'typedoc' if has_dependency?('typedoc')
152
+
153
+ docs_info
154
+ end
155
+
156
+ def detect_testing_framework
157
+ frameworks = []
158
+
159
+ # Ruby testing
160
+ frameworks << 'RSpec' if has_dependency?('rspec') || directory_exists?('spec')
161
+ frameworks << 'Minitest' if has_file?('test/test_helper.rb') || directory_exists?('test')
162
+
163
+ # JavaScript/TypeScript testing
164
+ frameworks << 'Jest' if has_dependency?('jest')
165
+ frameworks << 'Mocha' if has_dependency?('mocha')
166
+ frameworks << 'Cypress' if has_dependency?('cypress')
167
+ frameworks << 'Playwright' if has_dependency?('playwright')
168
+ frameworks << 'Vitest' if has_dependency?('vitest')
169
+
170
+ # Python testing
171
+ frameworks << 'pytest' if has_dependency?('pytest')
172
+ frameworks << 'unittest' if has_files_with_pattern?('test_*.py')
173
+
174
+ frameworks
175
+ end
176
+
177
+ def detect_build_system
178
+ systems = []
179
+
180
+ # JavaScript/Node.js
181
+ systems << 'npm' if has_file?('package.json')
182
+ systems << 'yarn' if has_file?('yarn.lock')
183
+ systems << 'pnpm' if has_file?('pnpm-lock.yaml')
184
+
185
+ # Ruby
186
+ systems << 'bundler' if has_file?('Gemfile')
187
+ systems << 'rake' if has_file?('Rakefile')
188
+
189
+ # Python
190
+ systems << 'pip' if has_file?('requirements.txt')
191
+ systems << 'poetry' if has_file?('pyproject.toml')
192
+ systems << 'pipenv' if has_file?('Pipfile')
193
+
194
+ # Other
195
+ systems << 'make' if has_file?('Makefile')
196
+ systems << 'gradle' if has_file?('build.gradle')
197
+ systems << 'maven' if has_file?('pom.xml')
198
+ systems << 'cargo' if has_file?('Cargo.toml')
199
+
200
+ systems
201
+ end
202
+
203
+ def detect_deployment_config
204
+ configs = []
205
+
206
+ # Container orchestration
207
+ configs << 'kubernetes' if has_file?('kustomization.yaml') || directory_exists?('k8s')
208
+ configs << 'docker-compose' if has_file?('docker-compose.yml') || has_file?('docker-compose.yaml')
209
+
210
+ # Platform-specific
211
+ configs << 'heroku' if has_file?('Procfile') || has_file?('app.json')
212
+ configs << 'vercel' if has_file?('vercel.json')
213
+ configs << 'netlify' if has_file?('netlify.toml') || has_file?('_redirects')
214
+ configs << 'railway' if has_file?('railway.json')
215
+ configs << 'fly.io' if has_file?('fly.toml')
216
+
217
+ # Infrastructure as Code
218
+ configs << 'terraform' if has_files_with_extension?('.tf')
219
+ configs << 'ansible' if has_files_with_extension?('.yml') && directory_exists?('playbooks')
220
+
221
+ # Rails specific
222
+ configs << 'kamal' if has_file?('config/deploy.yml')
223
+
224
+ configs
225
+ end
226
+
227
+ def detect_database
228
+ databases = []
229
+
230
+ # Check database gems/packages
231
+ databases << 'PostgreSQL' if has_dependency?('pg') || has_dependency?('postgres')
232
+ databases << 'MySQL' if has_dependency?('mysql2') || has_dependency?('mysql')
233
+ databases << 'SQLite' if has_dependency?('sqlite3')
234
+ databases << 'MongoDB' if has_dependency?('mongoid') || has_dependency?('mongoose')
235
+ databases << 'Redis' if has_dependency?('redis')
236
+
237
+ # Check configuration files
238
+ if has_file?('config/database.yml')
239
+ db_config = safe_load_yaml('config/database.yml')
240
+ if db_config
241
+ adapters = db_config.values.map { |env| env['adapter'] if env.is_a?(Hash) }.compact.uniq
242
+ databases.concat(adapters.map(&:capitalize))
243
+ end
244
+ end
245
+
246
+ databases.uniq
247
+ end
248
+
249
+ def detect_frontend_framework
250
+ return 'React' if react_project?
251
+ return 'Vue.js' if vue_project?
252
+ return 'Angular' if angular_project?
253
+ return 'Svelte' if has_dependency?('svelte')
254
+ return 'Alpine.js' if has_dependency?('alpinejs')
255
+ return 'Stimulus' if has_dependency?('@hotwired/stimulus')
256
+ return 'Hotwire' if has_dependency?('@hotwired/turbo-rails')
257
+
258
+ nil
259
+ end
260
+
261
+ def analyze_project_structure
262
+ structure = {
263
+ monorepo: monorepo?,
264
+ has_src_dir: directory_exists?('src'),
265
+ has_lib_dir: directory_exists?('lib'),
266
+ has_app_dir: directory_exists?('app'),
267
+ has_public_dir: directory_exists?('public'),
268
+ has_config_dir: directory_exists?('config'),
269
+ estimated_size: estimate_project_size
270
+ }
271
+
272
+ structure
273
+ end
274
+
275
+ def analyze_git_info
276
+ return {} unless directory_exists?('.git')
277
+
278
+ {
279
+ has_git: true,
280
+ has_gitignore: has_file?('.gitignore'),
281
+ has_github_actions: directory_exists?('.github/workflows'),
282
+ has_pre_commit: has_file?('.pre-commit-config.yaml')
283
+ }
284
+ end
285
+
286
+ def detect_package_managers
287
+ managers = []
288
+ managers << 'bundler' if has_file?('Gemfile')
289
+ managers << 'npm' if has_file?('package.json')
290
+ managers << 'yarn' if has_file?('yarn.lock')
291
+ managers << 'pnpm' if has_file?('pnpm-lock.yaml')
292
+ managers << 'pip' if has_file?('requirements.txt')
293
+ managers << 'poetry' if has_file?('pyproject.toml')
294
+ managers << 'cargo' if has_file?('Cargo.toml')
295
+ managers
296
+ end
297
+
298
+ def detect_ci_cd
299
+ ci_systems = []
300
+ ci_systems << 'GitHub Actions' if directory_exists?('.github/workflows')
301
+ ci_systems << 'GitLab CI' if has_file?('.gitlab-ci.yml')
302
+ ci_systems << 'CircleCI' if has_file?('.circleci/config.yml')
303
+ ci_systems << 'Travis CI' if has_file?('.travis.yml')
304
+ ci_systems << 'Jenkins' if has_file?('Jenkinsfile')
305
+ ci_systems
306
+ end
307
+
308
+ def detect_containerization
309
+ containers = []
310
+ containers << 'Docker' if has_file?('Dockerfile')
311
+ containers << 'Docker Compose' if has_file?('docker-compose.yml')
312
+ containers << 'Podman' if has_file?('Containerfile')
313
+ containers
314
+ end
315
+
316
+ def recommend_agents
317
+ agents = []
318
+
319
+ case @analysis_results[:project_type] || detect_project_type
320
+ when 'rails'
321
+ agents = ['backend', 'frontend', 'qa']
322
+ agents << 'ux' if has_files_with_extension?('.erb') || has_files_with_extension?('.slim')
323
+ when 'react', 'vue', 'angular', 'nextjs'
324
+ agents = ['frontend', 'ux', 'qa']
325
+ agents << 'backend' if has_api_routes?
326
+ when 'django', 'flask', 'express'
327
+ agents = ['backend', 'qa']
328
+ agents << 'frontend' if has_templates?
329
+ when 'static_site'
330
+ agents = ['ux', 'frontend']
331
+ when 'ruby_gem', 'npm_package', 'python_package'
332
+ agents = ['backend', 'qa']
333
+ else
334
+ agents = ['general', 'qa']
335
+ end
336
+
337
+ agents.uniq
338
+ end
339
+
340
+ def generate_smart_commands
341
+ commands = {}
342
+
343
+ # Test commands
344
+ if @analysis_results[:testing_framework]&.include?('RSpec')
345
+ commands[:test] = 'bundle exec rspec'
346
+ elsif @analysis_results[:testing_framework]&.include?('Minitest')
347
+ commands[:test] = 'bundle exec ruby -Itest test/test_helper.rb'
348
+ elsif @analysis_results[:testing_framework]&.include?('Jest')
349
+ commands[:test] = 'npm test'
350
+ elsif @analysis_results[:testing_framework]&.include?('pytest')
351
+ commands[:test] = 'pytest'
352
+ else
353
+ commands[:test] = detect_test_command_from_package_json || 'echo "No test command configured"'
354
+ end
355
+
356
+ # Lint commands
357
+ commands[:lint] = detect_lint_command
358
+
359
+ # Build commands
360
+ commands[:build] = detect_build_command
361
+
362
+ # Start commands
363
+ commands[:start] = detect_start_command
364
+
365
+ commands
366
+ end
367
+
368
+ # Helper methods
369
+ def rails_project?
370
+ has_file?('Gemfile') && (has_dependency?('rails') || has_file?('config/application.rb'))
371
+ end
372
+
373
+ def react_project?
374
+ has_dependency?('react')
375
+ end
376
+
377
+ def vue_project?
378
+ has_dependency?('vue')
379
+ end
380
+
381
+ def angular_project?
382
+ has_dependency?('@angular/core')
383
+ end
384
+
385
+ def nextjs_project?
386
+ has_dependency?('next')
387
+ end
388
+
389
+ def django_project?
390
+ has_file?('manage.py') && has_files_with_pattern?('*/settings.py')
391
+ end
392
+
393
+ def flask_project?
394
+ has_dependency?('flask')
395
+ end
396
+
397
+ def express_project?
398
+ has_dependency?('express')
399
+ end
400
+
401
+ def ruby_gem?
402
+ has_file?('*.gemspec') || (has_file?('Gemfile') && has_file?('lib/**/*.rb'))
403
+ end
404
+
405
+ def npm_package?
406
+ has_file?('package.json') && !has_dependency?('react') && !has_dependency?('vue')
407
+ end
408
+
409
+ def python_package?
410
+ has_file?('setup.py') || has_file?('pyproject.toml')
411
+ end
412
+
413
+ def static_site?
414
+ has_file?('index.html') && !has_file?('package.json') && !has_file?('Gemfile')
415
+ end
416
+
417
+ def monorepo?
418
+ package_json_dirs = Dir.glob(@project_root.join('*/package.json')).size
419
+ gemfile_dirs = Dir.glob(@project_root.join('*/Gemfile')).size
420
+
421
+ package_json_dirs > 1 || gemfile_dirs > 1
422
+ end
423
+
424
+ def has_file?(pattern)
425
+ Dir.glob(@project_root.join(pattern)).any?
426
+ end
427
+
428
+ def directory_exists?(path)
429
+ @project_root.join(path).directory?
430
+ end
431
+
432
+ def has_files_with_extension?(extension)
433
+ Dir.glob(@project_root.join("**/*#{extension}")).any?
434
+ end
435
+
436
+ def has_files_with_pattern?(pattern)
437
+ Dir.glob(@project_root.join(pattern)).any?
438
+ end
439
+
440
+ def has_dependency?(gem_or_package)
441
+ # Check Gemfile
442
+ if has_file?('Gemfile')
443
+ gemfile_content = safe_read_file('Gemfile')
444
+ return true if gemfile_content&.include?("'#{gem_or_package}'") || gemfile_content&.include?("\"#{gem_or_package}\"")
445
+ end
446
+
447
+ # Check package.json
448
+ if has_file?('package.json')
449
+ package_json = safe_load_json('package.json')
450
+ if package_json
451
+ deps = package_json.dig('dependencies') || {}
452
+ dev_deps = package_json.dig('devDependencies') || {}
453
+ return deps.key?(gem_or_package) || dev_deps.key?(gem_or_package)
454
+ end
455
+ end
456
+
457
+ false
458
+ end
459
+
460
+ def has_api_routes?
461
+ # Rails API routes
462
+ return true if has_file?('config/routes.rb') && safe_read_file('config/routes.rb')&.include?('api')
463
+
464
+ # Express routes
465
+ return true if has_files_with_pattern?('**/routes/**/*.js')
466
+
467
+ false
468
+ end
469
+
470
+ def has_templates?
471
+ has_files_with_extension?('.erb') ||
472
+ has_files_with_extension?('.html') ||
473
+ has_files_with_extension?('.jinja2') ||
474
+ has_files_with_extension?('.ejs')
475
+ end
476
+
477
+ def detect_project_name
478
+ # Try package.json first
479
+ if has_file?('package.json')
480
+ package_json = safe_load_json('package.json')
481
+ return package_json['name'] if package_json&.dig('name')
482
+ end
483
+
484
+ # Try gemspec
485
+ gemspec_files = Dir.glob(@project_root.join('*.gemspec'))
486
+ if gemspec_files.any?
487
+ return File.basename(gemspec_files.first, '.gemspec')
488
+ end
489
+
490
+ # Fall back to directory name
491
+ @project_root.basename.to_s
492
+ end
493
+
494
+ def generate_project_description
495
+ type = @analysis_results[:project_type] || detect_project_type
496
+ stack = @analysis_results[:technology_stack] || []
497
+
498
+ case type
499
+ when 'rails'
500
+ "Ruby on Rails application with #{stack.join(', ')}"
501
+ when 'react'
502
+ "React application built with #{stack.join(', ')}"
503
+ when 'vue'
504
+ "Vue.js application using #{stack.join(', ')}"
505
+ when 'django'
506
+ "Django web application with #{stack.join(', ')}"
507
+ else
508
+ "Software project using #{stack.join(', ')}"
509
+ end
510
+ end
511
+
512
+ def recommend_agent_count
513
+ size = estimate_project_size
514
+
515
+ case size
516
+ when :small then 2
517
+ when :medium then 3
518
+ when :large then 4
519
+ else 3
520
+ end
521
+ end
522
+
523
+ def estimate_project_size
524
+ total_files = Dir.glob(@project_root.join('**/*')).reject { |f| File.directory?(f) }.size
525
+
526
+ case total_files
527
+ when 0..50 then :small
528
+ when 51..200 then :medium
529
+ when 201..500 then :large
530
+ else :extra_large
531
+ end
532
+ end
533
+
534
+ def detect_test_command_from_package_json
535
+ package_json = safe_load_json('package.json')
536
+ package_json&.dig('scripts', 'test')
537
+ end
538
+
539
+ def detect_lint_command
540
+ return 'bundle exec rubocop' if has_dependency?('rubocop')
541
+ return 'npm run lint' if has_package_script?('lint')
542
+ return 'eslint .' if has_dependency?('eslint')
543
+ return 'flake8' if has_dependency?('flake8')
544
+
545
+ 'echo "No lint command configured"'
546
+ end
547
+
548
+ def detect_build_command
549
+ return 'npm run build' if has_package_script?('build')
550
+ return 'yarn build' if has_file?('yarn.lock') && has_package_script?('build')
551
+ return 'bundle exec rake build' if has_file?('Rakefile')
552
+
553
+ nil
554
+ end
555
+
556
+ def detect_start_command
557
+ if rails_project?
558
+ 'bundle exec rails server'
559
+ elsif has_package_script?('start')
560
+ 'npm start'
561
+ elsif has_package_script?('dev')
562
+ 'npm run dev'
563
+ else
564
+ nil
565
+ end
566
+ end
567
+
568
+ def has_package_script?(script_name)
569
+ package_json = safe_load_json('package.json')
570
+ package_json&.dig('scripts', script_name)
571
+ end
572
+
573
+ def analyze_readme_quality
574
+ return :none unless has_file?('README.md') || has_file?('README.rst') || has_file?('README.txt')
575
+
576
+ readme_files = ['README.md', 'README.rst', 'README.txt']
577
+ readme_content = readme_files.map { |f| safe_read_file(f) }.compact.first
578
+
579
+ return :minimal if !readme_content || readme_content.length < 100
580
+ return :good if readme_content.length > 500
581
+
582
+ :basic
583
+ end
584
+
585
+ def safe_read_file(path)
586
+ full_path = @project_root.join(path)
587
+ return nil unless full_path.file?
588
+
589
+ full_path.read
590
+ rescue StandardError
591
+ nil
592
+ end
593
+
594
+ def safe_load_json(path)
595
+ content = safe_read_file(path)
596
+ return nil unless content
597
+
598
+ JSON.parse(content)
599
+ rescue JSON::ParserError
600
+ nil
601
+ end
602
+
603
+ def safe_load_yaml(path)
604
+ content = safe_read_file(path)
605
+ return nil unless content
606
+
607
+ YAML.safe_load(content, aliases: true)
608
+ rescue Psych::SyntaxError, Psych::AliasesNotEnabled
609
+ nil
610
+ end
611
+ end
612
+ end