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.
- checksums.yaml +7 -0
- data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
- data/.enhance_swarm/user_patterns.json +37 -0
- data/CHANGELOG.md +184 -0
- data/LICENSE +21 -0
- data/PRODUCTION_TEST_LOG.md +502 -0
- data/README.md +905 -0
- data/Rakefile +28 -0
- data/USAGE_EXAMPLES.md +477 -0
- data/examples/enhance_workflow.md +346 -0
- data/examples/rails_project.md +253 -0
- data/exe/enhance-swarm +30 -0
- data/lib/enhance_swarm/additional_commands.rb +299 -0
- data/lib/enhance_swarm/agent_communicator.rb +460 -0
- data/lib/enhance_swarm/agent_reviewer.rb +283 -0
- data/lib/enhance_swarm/agent_spawner.rb +462 -0
- data/lib/enhance_swarm/cleanup_manager.rb +245 -0
- data/lib/enhance_swarm/cli.rb +1592 -0
- data/lib/enhance_swarm/command_executor.rb +78 -0
- data/lib/enhance_swarm/configuration.rb +324 -0
- data/lib/enhance_swarm/control_agent.rb +307 -0
- data/lib/enhance_swarm/dependency_validator.rb +195 -0
- data/lib/enhance_swarm/error_recovery.rb +785 -0
- data/lib/enhance_swarm/generator.rb +194 -0
- data/lib/enhance_swarm/interrupt_handler.rb +512 -0
- data/lib/enhance_swarm/logger.rb +106 -0
- data/lib/enhance_swarm/mcp_integration.rb +85 -0
- data/lib/enhance_swarm/monitor.rb +28 -0
- data/lib/enhance_swarm/notification_manager.rb +444 -0
- data/lib/enhance_swarm/orchestrator.rb +313 -0
- data/lib/enhance_swarm/output_streamer.rb +281 -0
- data/lib/enhance_swarm/process_monitor.rb +266 -0
- data/lib/enhance_swarm/progress_tracker.rb +215 -0
- data/lib/enhance_swarm/project_analyzer.rb +612 -0
- data/lib/enhance_swarm/resource_manager.rb +177 -0
- data/lib/enhance_swarm/retry_handler.rb +40 -0
- data/lib/enhance_swarm/session_manager.rb +247 -0
- data/lib/enhance_swarm/signal_handler.rb +95 -0
- data/lib/enhance_swarm/smart_defaults.rb +708 -0
- data/lib/enhance_swarm/task_integration.rb +150 -0
- data/lib/enhance_swarm/task_manager.rb +174 -0
- data/lib/enhance_swarm/version.rb +5 -0
- data/lib/enhance_swarm/visual_dashboard.rb +555 -0
- data/lib/enhance_swarm/web_ui.rb +211 -0
- data/lib/enhance_swarm.rb +69 -0
- data/setup.sh +86 -0
- data/sig/enhance_swarm.rbs +4 -0
- data/templates/claude/CLAUDE.md +160 -0
- data/templates/claude/MCP.md +117 -0
- data/templates/claude/PERSONAS.md +114 -0
- data/templates/claude/RULES.md +221 -0
- data/test_builtin_functionality.rb +121 -0
- data/test_core_components.rb +156 -0
- data/test_real_claude_integration.rb +285 -0
- data/test_security.rb +150 -0
- data/test_smart_defaults.rb +155 -0
- data/test_task_integration.rb +173 -0
- data/test_web_ui.rb +245 -0
- data/web/assets/css/main.css +645 -0
- data/web/assets/js/kanban.js +499 -0
- data/web/assets/js/main.js +525 -0
- data/web/templates/dashboard.html.erb +226 -0
- data/web/templates/kanban.html.erb +193 -0
- 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
|