ruby-ai-gem-context 0.1.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.
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ruby_llm"
4
+
5
+ module RubyAiGemContext
6
+ # Generates context files using RubyLLM
7
+ class Generator
8
+ attr_reader :platform, :file_collector, :options
9
+
10
+ # @param platform [Class] Platform class (e.g., Platforms::Claude)
11
+ # @param file_collector [FileCollector] Collector with project files
12
+ # @param options [Hash] Additional options
13
+ def initialize(platform:, file_collector:, **options)
14
+ @platform = platform
15
+ @file_collector = file_collector
16
+ @options = options
17
+ end
18
+
19
+ # Generate content for the platform
20
+ # @return [String] Generated content
21
+ def generate
22
+ files = file_collector.collect
23
+ prompt = build_prompt(files)
24
+
25
+ response = chat(prompt)
26
+ response.content
27
+ end
28
+
29
+ # Build the full prompt including project context
30
+ def build_prompt(files)
31
+ <<~PROMPT
32
+ #{platform.generation_prompt}
33
+
34
+ ---
35
+
36
+ Here is the project's source code and files for context:
37
+
38
+ #{format_files(files)}
39
+ PROMPT
40
+ end
41
+
42
+ private
43
+
44
+ def chat(prompt)
45
+ model = options[:model] || RubyAiGemContext.configuration.model
46
+
47
+ RubyLLM.chat(
48
+ model: model,
49
+ messages: [{ role: "user", content: prompt }],
50
+ temperature: RubyAiGemContext.configuration.temperature,
51
+ max_tokens: RubyAiGemContext.configuration.max_tokens
52
+ )
53
+ end
54
+
55
+ def format_files(files)
56
+ files.map do |path, content|
57
+ <<~FILE
58
+ ### #{path}
59
+
60
+ ```#{language_for(path)}
61
+ #{truncate_content(content)}
62
+ ```
63
+ FILE
64
+ end.join("\n")
65
+ end
66
+
67
+ def language_for(path)
68
+ case File.extname(path)
69
+ when ".rb" then "ruby"
70
+ when ".rake" then "ruby"
71
+ when ".gemspec" then "ruby"
72
+ when ".yml", ".yaml" then "yaml"
73
+ when ".json" then "json"
74
+ when ".md" then "markdown"
75
+ when ".txt" then "text"
76
+ else ""
77
+ end
78
+ end
79
+
80
+ def truncate_content(content, max_lines: 200)
81
+ lines = content.lines
82
+ if lines.size > max_lines
83
+ lines.first(max_lines).join + "\n# ... (truncated, #{lines.size - max_lines} more lines)"
84
+ else
85
+ content
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty-prompt"
4
+ require "tty-spinner"
5
+
6
+ module RubyAiGemContext
7
+ # Interactive terminal UI for rake tasks
8
+ class Interactive
9
+ attr_reader :prompt, :project_root
10
+
11
+ def initialize(project_root = Dir.pwd)
12
+ @prompt = TTY::Prompt.new
13
+ @project_root = project_root
14
+ end
15
+
16
+ # === Setup Task ===
17
+
18
+ # Ask which platforms to generate boilerplate for
19
+ def select_platforms
20
+ choices = RubyAiGemContext::PLATFORMS.map do |key, platform_class|
21
+ { name: "#{platform_class.name} (#{platform_class.filename})", value: key }
22
+ end
23
+
24
+ prompt.multi_select(
25
+ "Which platforms do you want to generate context files for?",
26
+ choices,
27
+ help: "(Use space to select, enter to confirm)",
28
+ min: 1
29
+ )
30
+ end
31
+
32
+ # Handle collision when file exists
33
+ def handle_collision(platform_class)
34
+ prompt.select(
35
+ "#{platform_class.filename} already exists. What would you like to do?",
36
+ [
37
+ { name: "Skip (keep existing)", value: :skip },
38
+ { name: "Backup & overwrite (saves #{platform_class.filename}.backup)", value: :backup },
39
+ { name: "Show diff (compare before deciding)", value: :diff },
40
+ { name: "Overwrite (replace existing)", value: :overwrite }
41
+ ]
42
+ )
43
+ end
44
+
45
+ # Show diff between existing and new content
46
+ def show_diff(existing, new_content)
47
+ puts "\n--- Existing content ---"
48
+ puts existing.lines.first(20).join
49
+ puts "..." if existing.lines.size > 20
50
+ puts "\n--- New content ---"
51
+ puts new_content.lines.first(20).join
52
+ puts "..." if new_content.lines.size > 20
53
+ puts
54
+
55
+ prompt.select("What would you like to do?", [
56
+ { name: "Keep existing", value: :skip },
57
+ { name: "Use new content", value: :overwrite },
58
+ { name: "Backup & use new", value: :backup }
59
+ ])
60
+ end
61
+
62
+ # === Generate Task ===
63
+
64
+ # Ask about source folders
65
+ def select_source_folders
66
+ choice = prompt.select(
67
+ "Where should we read source files from?",
68
+ [
69
+ { name: "Project root (scan everything)", value: :root },
70
+ { name: "Specific folders (you'll provide a list)", value: :specific }
71
+ ]
72
+ )
73
+
74
+ case choice
75
+ when :root
76
+ nil # nil means scan from root
77
+ when :specific
78
+ ask_folder_list
79
+ end
80
+ end
81
+
82
+ # Ask for specific folder list
83
+ def ask_folder_list
84
+ input = prompt.ask(
85
+ "Enter folder paths (comma-separated, relative to project root):",
86
+ default: "lib, app"
87
+ )
88
+
89
+ input.split(",").map(&:strip).reject(&:empty?)
90
+ end
91
+
92
+ # Confirm source folders and show what will be scanned
93
+ def confirm_source_folders(folders, summary)
94
+ puts
95
+ puts "Will scan #{summary[:total_files]} files (#{format_size(summary[:total_size])})"
96
+ puts "File types: #{summary[:by_extension].map { |ext, count| "#{ext || 'no ext'}: #{count}" }.join(', ')}"
97
+ puts
98
+
99
+ prompt.yes?("Proceed with generation?")
100
+ end
101
+
102
+ # Select which existing platforms to regenerate
103
+ def select_platforms_to_regenerate(existing_platforms)
104
+ choices = existing_platforms.map do |key|
105
+ platform_class = RubyAiGemContext.platform(key)
106
+ { name: "#{platform_class.name} (#{platform_class.filename})", value: key }
107
+ end
108
+
109
+ prompt.multi_select(
110
+ "Which context files do you want to regenerate?",
111
+ choices,
112
+ help: "(Use space to select, enter to confirm)"
113
+ )
114
+ end
115
+
116
+ # === Generate for Gem Task ===
117
+
118
+ # Ask for gem name
119
+ def ask_gem_name
120
+ prompt.ask("Enter the gem name:") do |q|
121
+ q.required true
122
+ q.validate(/\A[\w-]+\z/, "Invalid gem name format")
123
+ end
124
+ end
125
+
126
+ # === Progress Indicators ===
127
+
128
+ # Show spinner while doing work
129
+ def with_spinner(message)
130
+ spinner = TTY::Spinner.new("[:spinner] #{message}", format: :dots)
131
+ spinner.auto_spin
132
+ result = yield
133
+ spinner.success("Done!")
134
+ result
135
+ rescue StandardError => e
136
+ spinner.error("Failed!")
137
+ raise e
138
+ end
139
+
140
+ # === Output Helpers ===
141
+
142
+ def success(message)
143
+ puts prompt.decorate("✓ #{message}", :green)
144
+ end
145
+
146
+ def warning(message)
147
+ puts prompt.decorate("⚠ #{message}", :yellow)
148
+ end
149
+
150
+ def error(message)
151
+ puts prompt.decorate("✗ #{message}", :red)
152
+ end
153
+
154
+ def info(message)
155
+ puts prompt.decorate("ℹ #{message}", :cyan)
156
+ end
157
+
158
+ def header(message)
159
+ puts
160
+ puts prompt.decorate("═" * 50, :blue)
161
+ puts prompt.decorate(" #{message}", :blue, :bold)
162
+ puts prompt.decorate("═" * 50, :blue)
163
+ puts
164
+ end
165
+
166
+ def review_reminder
167
+ puts
168
+ warning "IMPORTANT: Review the generated files carefully!"
169
+ puts " AI-generated context is a first draft. The quality of your future"
170
+ puts " AI interactions depends on the accuracy of these files."
171
+ puts
172
+ puts " Suggested review steps:"
173
+ puts " 1. Read through each generated file"
174
+ puts " 2. Fix any inaccuracies or missing information"
175
+ puts " 3. Add project-specific details the AI might have missed"
176
+ puts " 4. Remove any incorrect assumptions"
177
+ puts
178
+ end
179
+
180
+ private
181
+
182
+ def format_size(bytes)
183
+ if bytes < 1024
184
+ "#{bytes} B"
185
+ elsif bytes < 1024 * 1024
186
+ "#{(bytes / 1024.0).round(1)} KB"
187
+ else
188
+ "#{(bytes / (1024.0 * 1024)).round(1)} MB"
189
+ end
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAiGemContext
4
+ # Base class for AI platform definitions
5
+ #
6
+ # Each platform (Claude, Cursor, Windsurf, etc.) defines:
7
+ # - The filename and location for context files
8
+ # - The template/format for the context file
9
+ # - The prompt to use when generating content
10
+ class Platform
11
+ class << self
12
+ # Human-readable name of the platform
13
+ def name
14
+ raise NotImplementedError
15
+ end
16
+
17
+ # The filename for the context file (e.g., "CLAUDE.md", ".cursorrules")
18
+ def filename
19
+ raise NotImplementedError
20
+ end
21
+
22
+ # Where the file should be placed relative to project root
23
+ # Most platforms expect files at the project root
24
+ def path
25
+ filename
26
+ end
27
+
28
+ # Description shown in the interactive menu
29
+ def description
30
+ raise NotImplementedError
31
+ end
32
+
33
+ # The prompt template for generating content
34
+ # This tells the AI what format and structure to use
35
+ def generation_prompt
36
+ raise NotImplementedError
37
+ end
38
+
39
+ # Template content for empty boilerplate
40
+ def template
41
+ raise NotImplementedError
42
+ end
43
+
44
+ # Check if the context file already exists
45
+ def exists?(project_root = Dir.pwd)
46
+ File.exist?(File.join(project_root, path))
47
+ end
48
+
49
+ # Full path to the context file
50
+ def full_path(project_root = Dir.pwd)
51
+ File.join(project_root, path)
52
+ end
53
+
54
+ # Write content to the context file
55
+ def write(content, project_root = Dir.pwd)
56
+ file_path = full_path(project_root)
57
+ FileUtils.mkdir_p(File.dirname(file_path))
58
+ File.write(file_path, content)
59
+ file_path
60
+ end
61
+
62
+ # Read existing content
63
+ def read(project_root = Dir.pwd)
64
+ return nil unless exists?(project_root)
65
+
66
+ File.read(full_path(project_root))
67
+ end
68
+
69
+ # Create backup of existing file
70
+ def backup(project_root = Dir.pwd)
71
+ return nil unless exists?(project_root)
72
+
73
+ backup_path = "#{full_path(project_root)}.backup"
74
+ FileUtils.cp(full_path(project_root), backup_path)
75
+ backup_path
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAiGemContext
4
+ module Platforms
5
+ class Claude < Platform
6
+ class << self
7
+ def name
8
+ "Claude Code"
9
+ end
10
+
11
+ def filename
12
+ "CLAUDE.md"
13
+ end
14
+
15
+ def description
16
+ "Claude Code / Claude AI - Project context file"
17
+ end
18
+
19
+ def generation_prompt
20
+ <<~PROMPT
21
+ Generate a CLAUDE.md file for this Ruby project. This file helps Claude Code understand the project.
22
+
23
+ Structure the file with these sections:
24
+
25
+ # Project Name
26
+
27
+ Brief description of what this project does.
28
+
29
+ ## Quick Start
30
+
31
+ How to set up and run the project (install deps, run tests, start server, etc.)
32
+
33
+ ## Architecture
34
+
35
+ Key architectural decisions, folder structure, important patterns used.
36
+
37
+ ## Code Style
38
+
39
+ Coding conventions, naming patterns, formatting preferences for this project.
40
+
41
+ ## Common Tasks
42
+
43
+ Frequent development tasks and how to do them (run tests, lint, deploy, etc.)
44
+
45
+ ## Key Files
46
+
47
+ Important files to understand the codebase (entry points, config, core classes).
48
+
49
+ ## Gotchas
50
+
51
+ Non-obvious things, common mistakes, edge cases to watch out for.
52
+
53
+ Keep it concise and practical. Focus on information that helps an AI assistant understand and work with this codebase effectively.
54
+ PROMPT
55
+ end
56
+
57
+ def template
58
+ <<~TEMPLATE
59
+ # Project Name
60
+
61
+ Brief description of what this project does.
62
+
63
+ ## Quick Start
64
+
65
+ ```bash
66
+ # Install dependencies
67
+ bundle install
68
+
69
+ # Run tests
70
+ bundle exec rspec
71
+
72
+ # Start development
73
+ # Add commands here
74
+ ```
75
+
76
+ ## Architecture
77
+
78
+ Describe the key architectural decisions and patterns used in this project.
79
+
80
+ ## Code Style
81
+
82
+ - Ruby style conventions used
83
+ - Naming patterns
84
+ - File organization
85
+
86
+ ## Common Tasks
87
+
88
+ - **Run tests**: `bundle exec rspec`
89
+ - **Run linter**: `bundle exec rubocop`
90
+ - **Generate docs**: `bundle exec yard`
91
+
92
+ ## Key Files
93
+
94
+ - `lib/` - Main library code
95
+ - `spec/` - Test files
96
+ - `Gemfile` - Dependencies
97
+
98
+ ## Gotchas
99
+
100
+ - Things to watch out for
101
+ - Common mistakes
102
+ - Non-obvious behaviors
103
+ TEMPLATE
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAiGemContext
4
+ module Platforms
5
+ class Codex < Platform
6
+ class << self
7
+ def name
8
+ "OpenAI Codex / ChatGPT"
9
+ end
10
+
11
+ def filename
12
+ "AGENTS.md"
13
+ end
14
+
15
+ def description
16
+ "OpenAI Codex / ChatGPT - Agents documentation"
17
+ end
18
+
19
+ def generation_prompt
20
+ <<~PROMPT
21
+ Generate an AGENTS.md file for this Ruby project. This file helps AI coding assistants (like ChatGPT, Codex, GitHub Copilot) understand this project.
22
+
23
+ Structure the file with:
24
+
25
+ # Project Name
26
+
27
+ Brief description of what this project does and its purpose.
28
+
29
+ ## Setup
30
+
31
+ How to install and configure the project.
32
+
33
+ ## Usage
34
+
35
+ Common usage patterns with code examples.
36
+
37
+ ## Architecture
38
+
39
+ How the code is organized, key abstractions, design patterns.
40
+
41
+ ## API Reference
42
+
43
+ Key classes/modules and their public interfaces.
44
+
45
+ ## Development
46
+
47
+ How to contribute: running tests, code style, PR process.
48
+
49
+ ## Examples
50
+
51
+ Practical code examples showing common use cases.
52
+
53
+ Focus on clarity and include runnable code examples where helpful.
54
+ PROMPT
55
+ end
56
+
57
+ def template
58
+ <<~TEMPLATE
59
+ # Project Name
60
+
61
+ Brief description of what this project does.
62
+
63
+ ## Setup
64
+
65
+ ```bash
66
+ # Install
67
+ gem install project-name
68
+ # or add to Gemfile
69
+ gem 'project-name'
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ ```ruby
75
+ require 'project_name'
76
+
77
+ # Basic usage example
78
+ ```
79
+
80
+ ## Architecture
81
+
82
+ Describe the overall architecture:
83
+ - Key modules and their responsibilities
84
+ - Design patterns used
85
+ - How components interact
86
+
87
+ ## API Reference
88
+
89
+ ### MainClass
90
+
91
+ ```ruby
92
+ # Constructor
93
+ MainClass.new(options)
94
+
95
+ # Key methods
96
+ instance.method_name(args)
97
+ ```
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ # Install dependencies
103
+ bundle install
104
+
105
+ # Run tests
106
+ bundle exec rspec
107
+
108
+ # Run linter
109
+ bundle exec rubocop
110
+ ```
111
+
112
+ ## Examples
113
+
114
+ ### Example 1: Basic Usage
115
+
116
+ ```ruby
117
+ # Show a complete working example
118
+ ```
119
+
120
+ ### Example 2: Advanced Usage
121
+
122
+ ```ruby
123
+ # Show more complex usage
124
+ ```
125
+ TEMPLATE
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyAiGemContext
4
+ module Platforms
5
+ class Cursor < Platform
6
+ class << self
7
+ def name
8
+ "Cursor"
9
+ end
10
+
11
+ def filename
12
+ ".cursorrules"
13
+ end
14
+
15
+ def description
16
+ "Cursor IDE - Project rules file"
17
+ end
18
+
19
+ def generation_prompt
20
+ <<~PROMPT
21
+ Generate a .cursorrules file for this Ruby project. This file tells Cursor AI how to work with this codebase.
22
+
23
+ The format should be clear instructions/rules, NOT markdown headers. Write it as a set of directives.
24
+
25
+ Include:
26
+
27
+ 1. Project overview - What this project is and does
28
+ 2. Tech stack - Ruby version, key gems, frameworks
29
+ 3. Code style rules - Naming conventions, formatting, patterns to follow
30
+ 4. File organization - Where different types of code live
31
+ 5. Testing approach - How to write tests, what framework is used
32
+ 6. Common patterns - Idioms and patterns used in this codebase
33
+ 7. Things to avoid - Anti-patterns, deprecated approaches
34
+ 8. Key commands - How to run tests, lint, build, etc.
35
+
36
+ Write in a direct, instructional tone. Each rule should be actionable.
37
+ Keep it focused on practical guidance for generating and modifying code.
38
+ PROMPT
39
+ end
40
+
41
+ def template
42
+ <<~TEMPLATE
43
+ # Project Overview
44
+ This is a Ruby project. Describe what it does here.
45
+
46
+ # Tech Stack
47
+ - Ruby 3.x
48
+ - RSpec for testing
49
+ - Add other key dependencies
50
+
51
+ # Code Style
52
+ - Use snake_case for methods and variables
53
+ - Use CamelCase for classes and modules
54
+ - Prefer single quotes for strings without interpolation
55
+ - Keep methods short and focused (under 10 lines ideally)
56
+ - Use meaningful variable names
57
+
58
+ # File Organization
59
+ - lib/ contains the main library code
60
+ - spec/ contains RSpec tests
61
+ - Each class gets its own file
62
+
63
+ # Testing
64
+ - Write RSpec tests for all new code
65
+ - Use describe/context/it blocks
66
+ - Prefer let over instance variables
67
+ - Test behavior, not implementation
68
+
69
+ # Patterns to Follow
70
+ - Dependency injection over global state
71
+ - Composition over inheritance
72
+ - Small, focused classes
73
+
74
+ # Things to Avoid
75
+ - Monkey patching core classes
76
+ - Global mutable state
77
+ - Deep inheritance hierarchies
78
+
79
+ # Commands
80
+ - Run tests: bundle exec rspec
81
+ - Run linter: bundle exec rubocop
82
+ - Install deps: bundle install
83
+ TEMPLATE
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end