Skip to content

Instantly share code, notes, and snippets.

@dhnaranjo
Last active August 3, 2023 20:15
Show Gist options
  • Save dhnaranjo/0fcd50eca2c57010421df370f96acbc8 to your computer and use it in GitHub Desktop.
Save dhnaranjo/0fcd50eca2c57010421df370f96acbc8 to your computer and use it in GitHub Desktop.
A cute AI-powered junior engineer to maybe do something useful.
# Required gems:
# - asimov
# - fast_ignore
class RoboJunior
def get_to_work
puts(changes)
Changer.new.change_stuff(changes)
end
def changes
@changes ||= Asker.new.chat(Teller.new.content)
end
# If you wanna paste it into the web interface.
def changes_to_clipboard
IO.popen("pbcopy", "w") { |f| f << teller.content }
end
end
class Asker
def chat(content)
response = request(content)
reply(response)
end
private
def request(content)
client.chat.create_completions(
model: model,
messages: [
{role: "user", content: content}
]
)
end
def reply(response)
response["choices"].first.dig("message", "content")
end
def model
"gpt-3.5-turbo"
end
def client
@openai_client ||= Asimov::Client.new(
api_key: ENV["OPENAI_API_KEY"],
request_options: {
timeout: 300
}
)
end
end
class Changer
def change_stuff(changes)
changes
.split("----")
.compact_blank
.map(&:strip)
.map { |change| write_change(change) }
end
def write_change(change)
relative_path = change.lines[0].strip
file_path = File.join(repo_root, relative_path)
content = change.lines[1..].join
FileUtils.mkdir_p(File.dirname(file_path))
File.write(file_path, content)
rescue => e
puts(e)
puts(e.message)
binding.break
end
def repo_root
Rails.root
end
end
class Teller
INCLUDE_RULES = <<~INCLUDE_FILES
app/models/**/*.rb
test/models/**/*_test.rb
Gemfile
INCLUDE_FILES
def content
instructions + files_string
end
def files_string
files_contents.join("\n")
end
def instructions
<<~INSTRUCTIONS
I want you to act as a full stack developer working in Ruby on Rails verson 7.0 and Ruby 3.2.
I will provide a codebase with a number of TODOs for you to complete.
#{todo_completion}
#{model_creation}
#{test_creation}
#{general_guidelines}
#{reply_formatting}
INSTRUCTIONS
end
def reply_formatting
<<~REPLY_FORMATTING
When replying to this message, please format your response as follows:
- Include any files you created or modified
- Do not include any files you did not create or modify
- Prepend each file with `----` on a line by itself
- Prepend each file with the file path on a line by itself
- Do not include any other text in your response
REPLY_FORMATTING
end
def general_guidelines
<<~GENERAL_GUIDELINES
Also, please follow these general guidelines:
- Do not use any gems that are not already in the codebase
- Do not make any changes to the codebase that are not required to complete a TODO
GENERAL_GUIDELINES
end
def todo_completion
<<~TODO_COMPLETION
When completing a TODO you must:
- Complete the TODO
- Write a test to ensure the TODO is completed
- Remove the completed TODO from the codebase
TODO_COMPLETION
end
def model_creation
<<~MODEL_CREATION
When creating a model you must:
- Create a migration
- Create a model
- Create a factory
- Create a test
- All attributes should be required unless specified as optional
- All associations should be defined with `inverse_of: :<association>`
- All has_many associations should be defined with `dependent: :destroy`
- Only one model should be created per TODO
MODEL_CREATION
end
def test_creation
<<~TEST_CREATION
When creating a test you must:
- Use FactoryBot to create test data
- Use Faker to generate test data
- Use MiniTest assertions
- Use Shoulda Matchers to test validations and associations
TEST_CREATION
end
def files_contents
non_ignored_file_paths.map { |file_path| prepare_file(file_path) }
end
def prepare_file(file_path)
contents = File.read(file_path)
relative_path = file_path.relative_path_from(repo_root)
<<~FILE
----
#{relative_path}
#{contents}
FILE
end
def non_ignored_file_paths
FastIgnore
.new(include_rules: INCLUDE_RULES)
.map { Pathname(_1) }
end
def repo_root
Rails.root
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment