@@ -124,6 +124,14 @@ func (cm *ConversationManager) Hydrate(ctx context.Context) error {
return fmt.Errorf("failed to get conversation history: %w", err)
}
+ // Load cwd from conversation if available - must happen before generating system prompt
+ // so that the system prompt includes guidance files from the context directory
+ cwd := ""
+ if conversation.Cwd != nil {
+ cwd = *conversation.Cwd
+ }
+ cm.cwd = cwd
+
// Generate system prompt if missing:
// - For user-initiated conversations: full system prompt
// - For subagent conversations (has parent): minimal subagent prompt
@@ -147,19 +155,12 @@ func (cm *ConversationManager) Hydrate(ctx context.Context) error {
history, system := cm.partitionMessages(messages)
- // Load cwd from conversation if available
- cwd := ""
- if conversation.Cwd != nil {
- cwd = *conversation.Cwd
- }
-
cm.mu.Lock()
cm.history = history
cm.system = system
cm.hasConversationEvents = len(history) > 0
cm.lastActivity = time.Now()
cm.hydrated = true
- cm.cwd = cwd
cm.mu.Unlock()
cm.logSystemPromptState(system, len(messages))
@@ -1,6 +1,7 @@
package server
import (
+ "context"
"encoding/json"
"net/http"
"net/http/httptest"
@@ -8,6 +9,9 @@ import (
"path/filepath"
"strings"
"testing"
+
+ "shelley.exe.dev/db/generated"
+ "shelley.exe.dev/llm"
)
// TestWorkingDirectoryConfiguration tests that the working directory (cwd) setting
@@ -294,3 +298,78 @@ func TestConversationCwdReturnedInList(t *testing.T) {
t.Error("conversation not found in list")
}
}
+
+// TestSystemPromptUsesCwdFromConversation verifies that when a conversation
+// is created with a specific cwd, the system prompt is generated using that
+// directory (not the server's working directory). This tests the fix for
+// https://github.com/boldsoftware/shelley/issues/30
+func TestSystemPromptUsesCwdFromConversation(t *testing.T) {
+ // Create a temp directory with an AGENTS.md file
+ tmpDir, err := os.MkdirTemp("", "shelley_cwd_test")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ // Create an AGENTS.md file with unique content we can search for
+ agentsContent := "UNIQUE_MARKER_FOR_CWD_TEST_XYZ123: This is test guidance."
+ agentsFile := filepath.Join(tmpDir, "AGENTS.md")
+ if err := os.WriteFile(agentsFile, []byte(agentsContent), 0o644); err != nil {
+ t.Fatalf("failed to write AGENTS.md: %v", err)
+ }
+
+ h := NewTestHarness(t)
+ defer h.Close()
+
+ // Create a conversation with the temp directory as cwd
+ h.NewConversation("bash: echo hello", tmpDir)
+ h.WaitToolResult()
+
+ // Get the system prompt from the database
+ var messages []generated.Message
+ err = h.db.Queries(context.Background(), func(q *generated.Queries) error {
+ var qerr error
+ messages, qerr = q.ListMessages(context.Background(), h.ConversationID())
+ return qerr
+ })
+ if err != nil {
+ t.Fatalf("failed to get messages: %v", err)
+ }
+
+ // Find the system message
+ var systemPrompt string
+ for _, msg := range messages {
+ if msg.Type == "system" && msg.LlmData != nil {
+ var llmMsg llm.Message
+ if err := json.Unmarshal([]byte(*msg.LlmData), &llmMsg); err == nil {
+ for _, content := range llmMsg.Content {
+ if content.Type == llm.ContentTypeText {
+ systemPrompt = content.Text
+ break
+ }
+ }
+ }
+ break
+ }
+ }
+
+ if systemPrompt == "" {
+ t.Fatal("no system prompt found in messages")
+ }
+
+ // Verify the system prompt contains our unique marker from AGENTS.md
+ if !strings.Contains(systemPrompt, "UNIQUE_MARKER_FOR_CWD_TEST_XYZ123") {
+ t.Errorf("system prompt should contain content from AGENTS.md in the cwd directory")
+ // Log first 1000 chars to help debug
+ if len(systemPrompt) > 1000 {
+ t.Logf("system prompt (first 1000 chars): %s...", systemPrompt[:1000])
+ } else {
+ t.Logf("system prompt: %s", systemPrompt)
+ }
+ }
+
+ // Verify the working directory in the prompt is our temp directory
+ if !strings.Contains(systemPrompt, tmpDir) {
+ t.Errorf("system prompt should reference the cwd directory: %s", tmpDir)
+ }
+}
@@ -0,0 +1,72 @@
+package server
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+// TestSystemPromptIncludesCwdGuidanceFiles verifies that AGENTS.md from the working directory
+// is included in the generated system prompt.
+func TestSystemPromptIncludesCwdGuidanceFiles(t *testing.T) {
+ // Create a temp directory to serve as our "context directory"
+ tmpDir, err := os.MkdirTemp("", "shelley_test")
+ if err != nil {
+ t.Fatalf("failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ // Create an AGENTS.md file in the temp directory
+ agentsContent := "TEST_UNIQUE_CONTENT_12345: Always use Go for everything."
+ agentsFile := filepath.Join(tmpDir, "AGENTS.md")
+ if err := os.WriteFile(agentsFile, []byte(agentsContent), 0o644); err != nil {
+ t.Fatalf("failed to write AGENTS.md: %v", err)
+ }
+
+ // Generate system prompt for this directory
+ prompt, err := GenerateSystemPrompt(tmpDir)
+ if err != nil {
+ t.Fatalf("GenerateSystemPrompt failed: %v", err)
+ }
+
+ // Verify the unique content from AGENTS.md is included in the prompt
+ if !strings.Contains(prompt, "TEST_UNIQUE_CONTENT_12345") {
+ t.Errorf("system prompt should contain content from AGENTS.md in the working directory")
+ t.Logf("AGENTS.md content: %s", agentsContent)
+ t.Logf("Generated prompt (first 2000 chars): %s", prompt[:min(len(prompt), 2000)])
+ }
+
+ // Verify the file path is mentioned in guidance section
+ if !strings.Contains(prompt, agentsFile) {
+ t.Errorf("system prompt should reference the AGENTS.md file path")
+ }
+}
+
+// TestSystemPromptEmptyCwdFallsBackToCurrentDir verifies that an empty workingDir
+// causes GenerateSystemPrompt to use the current directory.
+func TestSystemPromptEmptyCwdFallsBackToCurrentDir(t *testing.T) {
+ // Get current directory for comparison
+ currentDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("failed to get current directory: %v", err)
+ }
+
+ // Generate system prompt with empty workingDir
+ prompt, err := GenerateSystemPrompt("")
+ if err != nil {
+ t.Fatalf("GenerateSystemPrompt failed: %v", err)
+ }
+
+ // Verify the current directory is mentioned in the prompt
+ if !strings.Contains(prompt, currentDir) {
+ t.Errorf("system prompt should contain current directory when cwd is empty")
+ }
+}
+
+func min(a, b int) int {
+ if a < b {
+ return a
+ }
+ return b
+}