claude-on-rails 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +58 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +6 -3
- data/Gemfile.lock +346 -0
- data/README.md +4 -3
- data/Rakefile +15 -5
- data/lib/claude_on_rails/configuration.rb +8 -6
- data/lib/claude_on_rails/project_analyzer.rb +53 -65
- data/lib/claude_on_rails/swarm_builder.rb +80 -88
- data/lib/claude_on_rails/version.rb +4 -2
- data/lib/claude_on_rails.rb +11 -12
- data/lib/generators/claude_on_rails/swarm/swarm_generator.rb +74 -53
- data/lib/generators/claude_on_rails/swarm/templates/CLAUDE.md.erb +2 -100
- data/lib/generators/claude_on_rails/swarm/templates/claude_on_rails_context.md +32 -0
- metadata +12 -108
- data/claude-on-rails.gemspec +0 -45
@@ -1,159 +1,147 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ClaudeOnRails
|
2
4
|
class ProjectAnalyzer
|
3
5
|
attr_reader :root_path
|
4
|
-
|
6
|
+
|
5
7
|
def initialize(root_path)
|
6
8
|
@root_path = root_path
|
7
9
|
end
|
8
|
-
|
10
|
+
|
9
11
|
def analyze
|
10
12
|
{
|
11
13
|
api_only: api_only?,
|
12
14
|
test_framework: detect_test_framework,
|
13
|
-
has_graphql:
|
14
|
-
has_turbo:
|
15
|
-
has_devise:
|
16
|
-
has_sidekiq:
|
15
|
+
has_graphql: graphql?,
|
16
|
+
has_turbo: turbo?,
|
17
|
+
has_devise: devise?,
|
18
|
+
has_sidekiq: sidekiq?,
|
17
19
|
javascript_framework: detect_javascript_framework,
|
18
20
|
database: detect_database,
|
19
21
|
deployment: detect_deployment_method,
|
20
22
|
custom_patterns: detect_custom_patterns
|
21
23
|
}
|
22
24
|
end
|
23
|
-
|
25
|
+
|
24
26
|
private
|
25
|
-
|
27
|
+
|
26
28
|
def api_only?
|
27
29
|
application_rb = File.join(root_path, 'config', 'application.rb')
|
28
30
|
return false unless File.exist?(application_rb)
|
29
|
-
|
31
|
+
|
30
32
|
File.read(application_rb).include?('config.api_only = true')
|
31
33
|
end
|
32
|
-
|
34
|
+
|
33
35
|
def detect_test_framework
|
34
36
|
if File.exist?(File.join(root_path, 'spec'))
|
35
37
|
'RSpec'
|
36
38
|
elsif File.exist?(File.join(root_path, 'test'))
|
37
39
|
'Minitest'
|
38
|
-
else
|
39
|
-
nil
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
43
|
-
def
|
42
|
+
|
43
|
+
def graphql?
|
44
44
|
gemfile_path = File.join(root_path, 'Gemfile')
|
45
45
|
return false unless File.exist?(gemfile_path)
|
46
|
-
|
46
|
+
|
47
47
|
gemfile_content = File.read(gemfile_path)
|
48
|
-
gemfile_content.include?('graphql') ||
|
48
|
+
gemfile_content.include?('graphql') ||
|
49
49
|
File.exist?(File.join(root_path, 'app', 'graphql'))
|
50
50
|
end
|
51
|
-
|
52
|
-
def
|
51
|
+
|
52
|
+
def turbo?
|
53
53
|
gemfile_path = File.join(root_path, 'Gemfile')
|
54
54
|
return false unless File.exist?(gemfile_path)
|
55
|
-
|
55
|
+
|
56
56
|
gemfile_content = File.read(gemfile_path)
|
57
|
-
gemfile_content.include?('turbo-rails') ||
|
57
|
+
gemfile_content.include?('turbo-rails') ||
|
58
58
|
gemfile_content.include?('stimulus-rails') ||
|
59
59
|
File.exist?(File.join(root_path, 'app', 'javascript', 'controllers'))
|
60
60
|
end
|
61
|
-
|
62
|
-
def
|
61
|
+
|
62
|
+
def devise?
|
63
63
|
gemfile_path = File.join(root_path, 'Gemfile')
|
64
64
|
return false unless File.exist?(gemfile_path)
|
65
|
-
|
65
|
+
|
66
66
|
File.read(gemfile_path).include?('devise')
|
67
67
|
end
|
68
|
-
|
69
|
-
def
|
68
|
+
|
69
|
+
def sidekiq?
|
70
70
|
gemfile_path = File.join(root_path, 'Gemfile')
|
71
71
|
return false unless File.exist?(gemfile_path)
|
72
|
-
|
72
|
+
|
73
73
|
File.read(gemfile_path).include?('sidekiq')
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
def detect_javascript_framework
|
77
77
|
package_json = File.join(root_path, 'package.json')
|
78
78
|
return 'importmap' unless File.exist?(package_json)
|
79
|
-
|
79
|
+
|
80
80
|
content = File.read(package_json)
|
81
|
-
|
82
|
-
when content.include?('webpack')
|
81
|
+
if content.include?('webpack')
|
83
82
|
'webpack'
|
84
|
-
|
83
|
+
elsif content.include?('esbuild')
|
85
84
|
'esbuild'
|
86
|
-
|
85
|
+
elsif content.include?('vite')
|
87
86
|
'vite'
|
88
87
|
else
|
89
88
|
'importmap'
|
90
89
|
end
|
91
90
|
end
|
92
|
-
|
91
|
+
|
93
92
|
def detect_database
|
94
93
|
database_yml = File.join(root_path, 'config', 'database.yml')
|
95
94
|
return 'sqlite3' unless File.exist?(database_yml)
|
96
|
-
|
95
|
+
|
97
96
|
content = File.read(database_yml)
|
98
|
-
|
99
|
-
when content.include?('postgresql')
|
97
|
+
if content.include?('postgresql')
|
100
98
|
'postgresql'
|
101
|
-
|
99
|
+
elsif content.include?('mysql2')
|
102
100
|
'mysql'
|
103
|
-
|
101
|
+
elsif content.include?('sqlite3')
|
104
102
|
'sqlite3'
|
105
103
|
else
|
106
104
|
'unknown'
|
107
105
|
end
|
108
106
|
end
|
109
|
-
|
107
|
+
|
110
108
|
def detect_deployment_method
|
111
109
|
# Check for various deployment configurations
|
112
110
|
return 'kubernetes' if File.exist?(File.join(root_path, 'k8s')) ||
|
113
|
-
|
111
|
+
File.exist?(File.join(root_path, 'kubernetes'))
|
114
112
|
return 'docker' if File.exist?(File.join(root_path, 'Dockerfile'))
|
115
113
|
return 'capistrano' if File.exist?(File.join(root_path, 'Capfile'))
|
116
114
|
return 'heroku' if File.exist?(File.join(root_path, 'Procfile'))
|
117
115
|
return 'kamal' if File.exist?(File.join(root_path, 'config', 'deploy.yml'))
|
118
|
-
|
116
|
+
|
119
117
|
'unknown'
|
120
118
|
end
|
121
|
-
|
119
|
+
|
122
120
|
def detect_custom_patterns
|
123
121
|
patterns = {}
|
124
|
-
|
122
|
+
|
125
123
|
# Check for service objects
|
126
|
-
if File.directory?(File.join(root_path, 'app', 'services'))
|
127
|
-
|
128
|
-
end
|
129
|
-
|
124
|
+
patterns[:has_services] = true if File.directory?(File.join(root_path, 'app', 'services'))
|
125
|
+
|
130
126
|
# Check for form objects
|
131
|
-
if File.directory?(File.join(root_path, 'app', 'forms'))
|
132
|
-
|
133
|
-
end
|
134
|
-
|
127
|
+
patterns[:has_forms] = true if File.directory?(File.join(root_path, 'app', 'forms'))
|
128
|
+
|
135
129
|
# Check for presenters/decorators
|
136
130
|
if File.directory?(File.join(root_path, 'app', 'presenters')) ||
|
137
131
|
File.directory?(File.join(root_path, 'app', 'decorators'))
|
138
132
|
patterns[:has_presenters] = true
|
139
133
|
end
|
140
|
-
|
134
|
+
|
141
135
|
# Check for query objects
|
142
|
-
if File.directory?(File.join(root_path, 'app', 'queries'))
|
143
|
-
|
144
|
-
end
|
145
|
-
|
136
|
+
patterns[:has_queries] = true if File.directory?(File.join(root_path, 'app', 'queries'))
|
137
|
+
|
146
138
|
# Check for policies (authorization)
|
147
|
-
if File.directory?(File.join(root_path, 'app', 'policies'))
|
148
|
-
|
149
|
-
end
|
150
|
-
|
139
|
+
patterns[:has_policies] = true if File.directory?(File.join(root_path, 'app', 'policies'))
|
140
|
+
|
151
141
|
# Check for serializers
|
152
|
-
if File.directory?(File.join(root_path, 'app', 'serializers'))
|
153
|
-
|
154
|
-
end
|
155
|
-
|
142
|
+
patterns[:has_serializers] = true if File.directory?(File.join(root_path, 'app', 'serializers'))
|
143
|
+
|
156
144
|
patterns
|
157
145
|
end
|
158
146
|
end
|
159
|
-
end
|
147
|
+
end
|
@@ -1,192 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ClaudeOnRails
|
2
4
|
class SwarmBuilder
|
3
5
|
attr_reader :project_analysis
|
4
|
-
|
6
|
+
|
5
7
|
def initialize(project_analysis)
|
6
8
|
@project_analysis = project_analysis
|
7
9
|
end
|
8
|
-
|
10
|
+
|
9
11
|
def build
|
10
12
|
{
|
11
13
|
version: 1,
|
12
14
|
swarm: {
|
13
|
-
name:
|
14
|
-
main:
|
15
|
+
name: 'Rails Development Team',
|
16
|
+
main: 'architect',
|
15
17
|
instances: build_instances
|
16
18
|
}
|
17
19
|
}
|
18
20
|
end
|
19
|
-
|
21
|
+
|
20
22
|
private
|
21
|
-
|
23
|
+
|
22
24
|
def build_instances
|
23
25
|
instances = {}
|
24
|
-
|
26
|
+
|
25
27
|
# Always include architect
|
26
28
|
instances[:architect] = build_architect
|
27
|
-
|
29
|
+
|
28
30
|
# Core agents
|
29
31
|
instances[:models] = build_models_agent
|
30
32
|
instances[:controllers] = build_controllers_agent
|
31
|
-
|
33
|
+
|
32
34
|
# Conditional agents
|
33
|
-
unless project_analysis[:api_only]
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
if project_analysis[:
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
if project_analysis[:has_graphql]
|
42
|
-
instances[:graphql] = build_graphql_agent
|
43
|
-
end
|
44
|
-
|
45
|
-
if project_analysis[:has_turbo] && !project_analysis[:api_only]
|
46
|
-
instances[:stimulus] = build_stimulus_agent
|
47
|
-
end
|
48
|
-
|
35
|
+
instances[:views] = build_views_agent unless project_analysis[:api_only]
|
36
|
+
|
37
|
+
instances[:api] = build_api_agent if project_analysis[:api_only]
|
38
|
+
|
39
|
+
instances[:graphql] = build_graphql_agent if project_analysis[:has_graphql]
|
40
|
+
|
41
|
+
instances[:stimulus] = build_stimulus_agent if project_analysis[:has_turbo] && !project_analysis[:api_only]
|
42
|
+
|
49
43
|
# Supporting agents
|
50
44
|
instances[:services] = build_services_agent
|
51
45
|
instances[:jobs] = build_jobs_agent
|
52
|
-
|
53
|
-
if project_analysis[:test_framework]
|
54
|
-
|
55
|
-
end
|
56
|
-
|
46
|
+
|
47
|
+
instances[:tests] = build_tests_agent if project_analysis[:test_framework]
|
48
|
+
|
57
49
|
instances[:devops] = build_devops_agent
|
58
|
-
|
50
|
+
|
59
51
|
instances
|
60
52
|
end
|
61
|
-
|
53
|
+
|
62
54
|
def build_architect
|
63
|
-
connections = [
|
64
|
-
connections <<
|
65
|
-
connections <<
|
66
|
-
connections <<
|
67
|
-
connections <<
|
68
|
-
connections <<
|
69
|
-
connections <<
|
70
|
-
connections <<
|
71
|
-
|
55
|
+
connections = %w[models controllers]
|
56
|
+
connections << 'views' unless project_analysis[:api_only]
|
57
|
+
connections << 'api' if project_analysis[:api_only]
|
58
|
+
connections << 'graphql' if project_analysis[:has_graphql]
|
59
|
+
connections << 'services'
|
60
|
+
connections << 'jobs'
|
61
|
+
connections << 'tests' if project_analysis[:test_framework]
|
62
|
+
connections << 'devops'
|
63
|
+
|
72
64
|
{
|
73
65
|
description: "Rails architect coordinating #{project_analysis[:api_only] ? 'API' : 'full-stack'} development",
|
74
|
-
directory:
|
66
|
+
directory: '.',
|
75
67
|
model: ClaudeOnRails.configuration.default_model,
|
76
68
|
connections: connections,
|
77
|
-
prompt_file:
|
69
|
+
prompt_file: '.claude-on-rails/prompts/architect.md',
|
78
70
|
vibe: ClaudeOnRails.configuration.vibe_mode
|
79
71
|
}
|
80
72
|
end
|
81
|
-
|
73
|
+
|
82
74
|
def build_models_agent
|
83
75
|
{
|
84
|
-
description:
|
85
|
-
directory:
|
76
|
+
description: 'ActiveRecord models, migrations, and database optimization specialist',
|
77
|
+
directory: './app/models',
|
86
78
|
model: ClaudeOnRails.configuration.default_model,
|
87
79
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
88
|
-
prompt_file:
|
80
|
+
prompt_file: '.claude-on-rails/prompts/models.md'
|
89
81
|
}
|
90
82
|
end
|
91
|
-
|
83
|
+
|
92
84
|
def build_controllers_agent
|
93
|
-
connections = [
|
94
|
-
connections <<
|
95
|
-
|
85
|
+
connections = ['services']
|
86
|
+
connections << 'api' if project_analysis[:api_only]
|
87
|
+
|
96
88
|
{
|
97
|
-
description:
|
98
|
-
directory:
|
89
|
+
description: 'Rails controllers, routing, and request handling specialist',
|
90
|
+
directory: './app/controllers',
|
99
91
|
model: ClaudeOnRails.configuration.default_model,
|
100
92
|
connections: connections.empty? ? nil : connections,
|
101
93
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
102
|
-
prompt_file:
|
94
|
+
prompt_file: '.claude-on-rails/prompts/controllers.md'
|
103
95
|
}.compact
|
104
96
|
end
|
105
|
-
|
97
|
+
|
106
98
|
def build_views_agent
|
107
99
|
connections = []
|
108
|
-
connections <<
|
109
|
-
|
100
|
+
connections << 'stimulus' if project_analysis[:has_turbo]
|
101
|
+
|
110
102
|
{
|
111
|
-
description:
|
112
|
-
directory:
|
103
|
+
description: 'Rails views, layouts, partials, and asset pipeline specialist',
|
104
|
+
directory: './app/views',
|
113
105
|
model: ClaudeOnRails.configuration.default_model,
|
114
106
|
connections: connections.empty? ? nil : connections,
|
115
107
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
116
|
-
prompt_file:
|
108
|
+
prompt_file: '.claude-on-rails/prompts/views.md'
|
117
109
|
}.compact
|
118
110
|
end
|
119
|
-
|
111
|
+
|
120
112
|
def build_api_agent
|
121
113
|
{
|
122
|
-
description:
|
123
|
-
directory:
|
114
|
+
description: 'RESTful API design, serialization, and versioning specialist',
|
115
|
+
directory: './app/controllers/api',
|
124
116
|
model: ClaudeOnRails.configuration.default_model,
|
125
117
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
126
|
-
prompt_file:
|
118
|
+
prompt_file: '.claude-on-rails/prompts/api.md'
|
127
119
|
}
|
128
120
|
end
|
129
|
-
|
121
|
+
|
130
122
|
def build_graphql_agent
|
131
123
|
{
|
132
|
-
description:
|
133
|
-
directory:
|
124
|
+
description: 'GraphQL schema, resolvers, and mutations specialist',
|
125
|
+
directory: './app/graphql',
|
134
126
|
model: ClaudeOnRails.configuration.default_model,
|
135
127
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
136
|
-
prompt_file:
|
128
|
+
prompt_file: '.claude-on-rails/prompts/graphql.md'
|
137
129
|
}
|
138
130
|
end
|
139
|
-
|
131
|
+
|
140
132
|
def build_stimulus_agent
|
141
133
|
{
|
142
|
-
description:
|
143
|
-
directory:
|
134
|
+
description: 'Stimulus.js controllers and Turbo integration specialist',
|
135
|
+
directory: './app/javascript',
|
144
136
|
model: ClaudeOnRails.configuration.default_model,
|
145
137
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
146
|
-
prompt_file:
|
138
|
+
prompt_file: '.claude-on-rails/prompts/stimulus.md'
|
147
139
|
}
|
148
140
|
end
|
149
|
-
|
141
|
+
|
150
142
|
def build_services_agent
|
151
143
|
{
|
152
|
-
description:
|
153
|
-
directory:
|
144
|
+
description: 'Service objects, business logic, and design patterns specialist',
|
145
|
+
directory: './app/services',
|
154
146
|
model: ClaudeOnRails.configuration.default_model,
|
155
147
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
156
|
-
prompt_file:
|
148
|
+
prompt_file: '.claude-on-rails/prompts/services.md'
|
157
149
|
}
|
158
150
|
end
|
159
|
-
|
151
|
+
|
160
152
|
def build_jobs_agent
|
161
153
|
{
|
162
|
-
description:
|
163
|
-
directory:
|
154
|
+
description: 'Background jobs, ActiveJob, and async processing specialist',
|
155
|
+
directory: './app/jobs',
|
164
156
|
model: ClaudeOnRails.configuration.default_model,
|
165
157
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
166
|
-
prompt_file:
|
158
|
+
prompt_file: '.claude-on-rails/prompts/jobs.md'
|
167
159
|
}
|
168
160
|
end
|
169
|
-
|
161
|
+
|
170
162
|
def build_tests_agent
|
171
163
|
test_dir = project_analysis[:test_framework] == 'RSpec' ? './spec' : './test'
|
172
|
-
|
164
|
+
|
173
165
|
{
|
174
166
|
description: "#{project_analysis[:test_framework]} testing, factories, and test coverage specialist",
|
175
167
|
directory: test_dir,
|
176
168
|
model: ClaudeOnRails.configuration.default_model,
|
177
169
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
178
|
-
prompt_file:
|
170
|
+
prompt_file: '.claude-on-rails/prompts/tests.md'
|
179
171
|
}
|
180
172
|
end
|
181
|
-
|
173
|
+
|
182
174
|
def build_devops_agent
|
183
175
|
{
|
184
|
-
description:
|
185
|
-
directory:
|
176
|
+
description: 'Deployment, Docker, CI/CD, and production configuration specialist',
|
177
|
+
directory: './config',
|
186
178
|
model: ClaudeOnRails.configuration.default_model,
|
187
179
|
allowed_tools: %w[Read Edit Write Bash Grep Glob LS],
|
188
|
-
prompt_file:
|
180
|
+
prompt_file: '.claude-on-rails/prompts/devops.md'
|
189
181
|
}
|
190
182
|
end
|
191
183
|
end
|
192
|
-
end
|
184
|
+
end
|
data/lib/claude_on_rails.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'claude_on_rails/version'
|
4
|
+
require 'claude_on_rails/configuration'
|
5
|
+
require 'claude_on_rails/project_analyzer'
|
6
|
+
require 'claude_on_rails/swarm_builder'
|
5
7
|
|
6
8
|
module ClaudeOnRails
|
7
9
|
class Error < StandardError; end
|
8
|
-
|
10
|
+
|
9
11
|
class << self
|
10
|
-
attr_accessor :configuration
|
11
|
-
|
12
12
|
def configure
|
13
|
-
self.configuration ||= Configuration.new
|
14
13
|
yield(configuration) if block_given?
|
15
14
|
end
|
16
|
-
|
15
|
+
|
17
16
|
def configuration
|
18
17
|
@configuration ||= Configuration.new
|
19
18
|
end
|
20
|
-
|
19
|
+
|
21
20
|
def analyze_project(root_path = Rails.root)
|
22
21
|
ProjectAnalyzer.new(root_path).analyze
|
23
22
|
end
|
24
|
-
|
23
|
+
|
25
24
|
def build_swarm(project_analysis)
|
26
25
|
SwarmBuilder.new(project_analysis).build
|
27
26
|
end
|
28
27
|
end
|
29
|
-
end
|
28
|
+
end
|