Commit b0eb9b4
Changed files (4)
lib
elelem
spec
lib/elelem/conversation.rb
@@ -56,7 +56,7 @@ module Elelem
"#{base}\n\nUse commands to deeply understand the system."
when [:execute, :write]
"#{base}\n\nCreate and execute freely. Have fun. Be kind."
- when [:read, :execute, :write]
+ when [:execute, :read, :write]
"#{base}\n\nYou have all tools. Use them wisely."
else
base
spec/elelem/agent_spec.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+RSpec.describe Elelem::Agent do
+ let(:mock_client) { double("client") }
+ let(:agent) { described_class.new(mock_client) }
+
+ describe "#initialize" do
+ it "creates a new conversation" do
+ expect(agent.conversation).to be_a(Elelem::Conversation)
+ end
+
+ it "stores the client" do
+ expect(agent.client).to eq(mock_client)
+ end
+
+ it "initializes tools for all modes" do
+ expect(agent.tools[:read]).to be_an(Array)
+ expect(agent.tools[:write]).to be_an(Array)
+ expect(agent.tools[:execute]).to be_an(Array)
+ end
+ end
+
+ describe "#tools_for" do
+ it "returns read tools for read mode" do
+ mode = Set[:read]
+ tools = agent.send(:tools_for, mode)
+
+ tool_names = tools.map { |t| t.dig(:function, :name) }
+ expect(tool_names).to include("grep", "list", "read")
+ expect(tool_names).not_to include("write", "patch", "execute")
+ end
+
+ it "returns write tools for write mode" do
+ mode = Set[:write]
+ tools = agent.send(:tools_for, mode)
+
+ tool_names = tools.map { |t| t.dig(:function, :name) }
+ expect(tool_names).to include("patch", "write")
+ expect(tool_names).not_to include("grep", "execute")
+ end
+
+ it "returns execute tools for execute mode" do
+ mode = Set[:execute]
+ tools = agent.send(:tools_for, mode)
+
+ tool_names = tools.map { |t| t.dig(:function, :name) }
+ expect(tool_names).to include("execute")
+ expect(tool_names).not_to include("grep", "write")
+ end
+
+ it "returns all tools for auto mode" do
+ mode = Set[:read, :write, :execute]
+ tools = agent.send(:tools_for, mode)
+
+ tool_names = tools.map { |t| t.dig(:function, :name) }
+ expect(tool_names).to include("grep", "list", "read", "patch", "write", "execute")
+ end
+
+ it "returns combined tools for build mode" do
+ mode = Set[:read, :write]
+ tools = agent.send(:tools_for, mode)
+
+ tool_names = tools.map { |t| t.dig(:function, :name) }
+ expect(tool_names).to include("grep", "read", "write", "patch")
+ expect(tool_names).not_to include("execute")
+ end
+ end
+
+ describe "integration with conversation" do
+ it "conversation uses mode-aware prompts" do
+ conversation = agent.conversation
+ conversation.add(role: :user, content: "test message")
+
+ read_history = conversation.history_for([:read])
+ write_history = conversation.history_for([:write])
+
+ expect(read_history[0][:content]).to include("Read and analyze")
+ expect(write_history[0][:content]).to include("Write clean, thoughtful code")
+ expect(read_history[0][:content]).not_to eq(write_history[0][:content])
+ end
+ end
+end
spec/elelem/conversation_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+RSpec.describe Elelem::Conversation do
+ let(:conversation) { described_class.new }
+
+ describe "#history_for" do
+ context "with empty conversation" do
+ it "returns history with mode-specific system prompt for read mode" do
+ history = conversation.history_for([:read])
+
+ expect(history.length).to eq(1)
+ expect(history[0][:role]).to eq("system")
+ expect(history[0][:content]).to include("Read and analyze")
+ end
+
+ it "returns history with mode-specific system prompt for write mode" do
+ history = conversation.history_for([:write])
+
+ expect(history[0][:content]).to include("Write clean, thoughtful code")
+ end
+
+ it "returns history with mode-specific system prompt for execute mode" do
+ history = conversation.history_for([:execute])
+
+ expect(history[0][:content]).to include("Use shell commands creatively")
+ end
+
+ it "returns history with mode-specific system prompt for read+write mode" do
+ history = conversation.history_for([:read, :write])
+
+ expect(history[0][:content]).to include("First understand, then build solutions")
+ end
+
+ it "returns history with mode-specific system prompt for read+execute mode" do
+ history = conversation.history_for([:read, :execute])
+
+ expect(history[0][:content]).to include("Use commands to deeply understand")
+ end
+
+ it "returns history with mode-specific system prompt for write+execute mode" do
+ history = conversation.history_for([:write, :execute])
+
+ expect(history[0][:content]).to include("Create and execute freely")
+ end
+
+ it "returns history with mode-specific system prompt for all tools mode" do
+ history = conversation.history_for([:read, :write, :execute])
+
+ expect(history[0][:content]).to include("You have all tools")
+ end
+
+ it "returns base system prompt for unknown mode" do
+ history = conversation.history_for([:unknown])
+
+ expect(history[0][:content]).not_to include("Read and analyze")
+ expect(history[0][:content]).not_to include("Write clean")
+ end
+
+ it "returns base system prompt for empty mode" do
+ history = conversation.history_for([])
+
+ expect(history[0][:role]).to eq("system")
+ expect(history[0][:content]).to be_a(String)
+ end
+ end
+
+ context "with mode order independence" do
+ it "returns same prompt for [:read, :write] and [:write, :read]" do
+ history1 = conversation.history_for([:read, :write])
+ history2 = conversation.history_for([:write, :read])
+
+ expect(history1[0][:content]).to eq(history2[0][:content])
+ end
+
+ it "returns same prompt for [:read, :execute] and [:execute, :read]" do
+ history1 = conversation.history_for([:read, :execute])
+ history2 = conversation.history_for([:execute, :read])
+
+ expect(history1[0][:content]).to eq(history2[0][:content])
+ end
+
+ it "returns same prompt for all permutations of [:read, :write, :execute]" do
+ history1 = conversation.history_for([:read, :write, :execute])
+ history2 = conversation.history_for([:execute, :read, :write])
+ history3 = conversation.history_for([:write, :execute, :read])
+
+ expect(history1[0][:content]).to eq(history2[0][:content])
+ expect(history2[0][:content]).to eq(history3[0][:content])
+ end
+ end
+
+ context "with populated conversation" do
+ before do
+ conversation.add(role: :user, content: "Hello")
+ conversation.add(role: :assistant, content: "Hi there")
+ end
+
+ it "preserves all conversation items" do
+ history = conversation.history_for([:read])
+
+ expect(history.length).to eq(3)
+ expect(history[1][:role]).to eq(:user)
+ expect(history[1][:content]).to eq("Hello")
+ expect(history[2][:role]).to eq(:assistant)
+ expect(history[2][:content]).to eq("Hi there")
+ end
+
+ it "updates system prompt without mutating original" do
+ original_items = conversation.instance_variable_get(:@items)
+ original_system_content = original_items[0][:content]
+
+ history = conversation.history_for([:read])
+
+ expect(history[0][:content]).not_to eq(original_system_content)
+ expect(original_items[0][:content]).to eq(original_system_content)
+ end
+
+ it "returns a copy, not the original array" do
+ history = conversation.history_for([:read])
+ original_items = conversation.instance_variable_get(:@items)
+
+ expect(history).not_to be(original_items)
+ end
+ end
+ end
+
+ describe "#add" do
+ it "adds user message to conversation" do
+ conversation.add(role: :user, content: "test message")
+ history = conversation.history_for([])
+
+ expect(history.length).to eq(2)
+ expect(history[1][:content]).to eq("test message")
+ end
+
+ it "merges consecutive messages with same role" do
+ conversation.add(role: :user, content: "part 1")
+ conversation.add(role: :user, content: "part 2")
+ history = conversation.history_for([])
+
+ expect(history.length).to eq(2)
+ expect(history[1][:content]).to eq("part 1part 2")
+ end
+
+ it "ignores nil content" do
+ conversation.add(role: :user, content: nil)
+ history = conversation.history_for([])
+
+ expect(history.length).to eq(1)
+ end
+
+ it "ignores empty content" do
+ conversation.add(role: :user, content: "")
+ history = conversation.history_for([])
+
+ expect(history.length).to eq(1)
+ end
+
+ it "raises error for unknown role" do
+ expect {
+ conversation.add(role: :unknown, content: "test")
+ }.to raise_error(/unknown role/)
+ end
+ end
+
+ describe "#clear" do
+ it "resets conversation to default context" do
+ conversation.add(role: :user, content: "test")
+ conversation.clear
+ history = conversation.history_for([])
+
+ expect(history.length).to eq(1)
+ expect(history[0][:role]).to eq("system")
+ end
+ end
+
+ describe "#dump" do
+ it "returns JSON representation with mode-specific prompt" do
+ conversation.add(role: :user, content: "test")
+ json = conversation.dump([:read])
+
+ parsed = JSON.parse(json)
+ expect(parsed).to be_an(Array)
+ expect(parsed.length).to eq(2)
+ expect(parsed[0]["content"]).to include("Read and analyze")
+ end
+ end
+end
spec/spec_helper.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require "elelem"
+require_relative "../lib/elelem"
RSpec.configure do |config|
# Enable flags like --only-failures and --next-failure