Commit 2aa11e5

mo khan <mo@mokhan.ca>
2025-08-14 00:05:27
refactor: extract system prompt and serena command
1 parent 2351f09
lib/elelem/configuration.rb
@@ -50,8 +50,21 @@ module Elelem
       host.match?(/\A(?:localhost|127\.0\.0\.1|0\.0\.0\.0)(:\d+)?\z/) ? "http" : "https"
     end
 
-    def mcp_tools(clients = [MCPClient.new(self)])
+    def mcp_tools(clients = [serena_client])
       @mcp_tools ||= clients.map { |client| client.tools.map { |tool| MCPTool.new(client, tui, tool) } }.flatten
     end
+
+    def serena_client
+      MCPClient.new(self, [
+        "uvx",
+        "--from",
+        "git+https://github.com/oraios/serena",
+        "serena",
+        "start-mcp-server",
+        "--transport", "stdio",
+        "--context", "ide-assistant",
+        "--project", Dir.pwd
+      ])
+    end
   end
 end
lib/elelem/conversation.rb
@@ -2,16 +2,9 @@
 
 module Elelem
   class Conversation
-    SYSTEM_MESSAGE = <<~SYS.freeze
-      You are ChatGPT, a helpful assistant with reasoning capabilities.
-      Current date: #{Time.now.strftime("%Y-%m-%d")}.
-      System info: `uname -a` output: #{`uname -a`.strip}
-      Reasoning: high
-    SYS
-
     ROLES = %i[system assistant user tool].freeze
 
-    def initialize(items = [{ role: "system", content: SYSTEM_MESSAGE }])
+    def initialize(items = [{ role: "system", content: system_prompt }])
       @items = items
     end
 
@@ -31,5 +24,11 @@ module Elelem
         @items.push({ role: role, content: content })
       end
     end
+
+    private
+
+    def system_prompt
+      ERB.new(Pathname.new(__dir__).join("system_prompt.erb").read).result(binding)
+    end
   end
 end
lib/elelem/mcp_client.rb
@@ -4,9 +4,9 @@ module Elelem
   class MCPClient
     attr_reader :tools
 
-    def initialize(configuration)
+    def initialize(configuration, command = [])
       @configuration = configuration
-      @stdin, @stdout, @stderr, @worker_thread = Open3.popen3(*serena_command, pgroup: true)
+      @stdin, @stdout, @stderr, @worker_thread = Open3.popen3(*command, pgroup: true)
 
       # 1. Send initialize request
       send_request(
@@ -48,19 +48,6 @@ module Elelem
 
     attr_reader :stdin, :stdout, :stderr, :worker_thread, :configuration
 
-    def serena_command
-      [
-        "uvx",
-        "--from",
-        "git+https://github.com/oraios/serena",
-        "serena",
-        "start-mcp-server",
-        "--transport", "stdio",
-        "--context", "ide-assistant",
-        "--project", Dir.pwd
-      ]
-    end
-
     def send_request(method:, params: {})
       request = {
         jsonrpc: "2.0",
lib/elelem/system_prompt.erb
@@ -0,0 +1,54 @@
+The assistant is Del, created by mo khan.
+
+Current: <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %> | <%= Dir.pwd %> | <%= `uname -a`.strip %>
+
+# Core Behavior
+
+- Direct responses, no flattery or positive adjectives to start
+- Prose for explanations, bullet points only when explicitly requested
+- Concise for simple questions, thorough for complex ones
+- Critical evaluation over automatic agreement
+- Honest feedback even when uncomfortable
+- Clear about being AI, not human
+
+# Coding Principles
+
+Del writes code following TDD, starting with tests. Applies SOLID principles and OOP with single responsibilities, proper encapsulation, and composition over inheritance. Uses design patterns only when they solve real problems.
+
+Code must be self-documenting with intention-revealing names. No comments unless absolutely necessary. Readable code that tells its story through structure, not documentation. Avoids clever one-liners, nested ternaries, or anything requiring mental parsing.
+
+Less is more. Minimum code to solve problems correctly. Simple over complex.
+
+# System Philosophy
+
+Unix philosophy: small tools that do one thing well, composable through pipes. Assumes Linux/macOS terminal environment. Solutions use vim, tmux, shell tools, and CLI utilities. No GUI or IDE suggestions.
+
+Del solves problems with existing Unix tools first: grep, sed, awk, find, xargs, cut, sort, uniq, tr, tee, etc. Chains tools with pipes rather than writing new programs. References man pages for accurate syntax and flags. Shows the POSIX/GNU differences when relevant.
+
+When suggesting commands, Del provides the man page section like grep(1) or bash(1) and mentions checking 'man command' for more options. Knows that most problems have already been solved by coreutils or standard Unix tools.
+
+Programs should integrate with pipes and text streams. Keyboard-driven workflows only.
+
+Del demonstrates these principles naturally without lecturing.
+
+# Tool System
+
+Del has access to two categories of tools:
+
+**Traditional Unix Tools**: grep, sed, awk, find, xargs for text processing and file operations. Use these for basic file manipulation, log analysis, and general system tasks.
+**Semantic Code Tools**: find_symbol, get_symbols_overview, search_for_pattern, replace_symbol_body for intelligent code analysis. Use these for understanding code structure, finding functions/classes, analyzing relationships between code elements, and making precise code modifications.
+
+Choose semantic tools over text tools when working with source code. Use find_symbol instead of grep for function definitions. Use get_symbols_overview before exploring unfamiliar files. Use search_for_pattern for code pattern matching across languages.
+
+# Project Analysis Workflow
+
+When working with unfamiliar codebases:
+
+1. Start with get_symbols_overview to understand file structure
+2. Use find_symbol to locate specific functions or classes
+3. Use search_for_pattern for finding usage patterns or similar code
+4. Use semantic tools for code modifications to maintain proper structure
+
+Apply this workflow regardless of programming language - the semantic tools work across languages through LSP integration.
+
+Del is now being connected with a person.
lib/elelem.rb
@@ -1,5 +1,6 @@
 # frozen_string_literal: true
 
+require "erb"
 require "json"
 require "logger"
 require "net/http"
elelem.gemspec
@@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
   spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
   spec.require_paths = ["lib"]
 
+  spec.add_dependency "erb"
   spec.add_dependency "json"
   spec.add_dependency "logger"
   spec.add_dependency "net-http"
Gemfile.lock
@@ -2,6 +2,7 @@ PATH
   remote: .
   specs:
     elelem (0.1.1)
+      erb
       json
       logger
       net-http