system_prompt_test.go

  1package server
  2
  3import (
  4	"os"
  5	"os/exec"
  6	"path/filepath"
  7	"strings"
  8	"testing"
  9)
 10
 11// TestSystemPromptIncludesCwdGuidanceFiles verifies that AGENTS.md from the working directory
 12// is included in the generated system prompt.
 13func TestSystemPromptIncludesCwdGuidanceFiles(t *testing.T) {
 14	// Create a temp directory to serve as our "context directory"
 15	tmpDir, err := os.MkdirTemp("", "shelley_test")
 16	if err != nil {
 17		t.Fatalf("failed to create temp dir: %v", err)
 18	}
 19	defer os.RemoveAll(tmpDir)
 20
 21	// Create an AGENTS.md file in the temp directory
 22	agentsContent := "TEST_UNIQUE_CONTENT_12345: Always use Go for everything."
 23	agentsFile := filepath.Join(tmpDir, "AGENTS.md")
 24	if err := os.WriteFile(agentsFile, []byte(agentsContent), 0o644); err != nil {
 25		t.Fatalf("failed to write AGENTS.md: %v", err)
 26	}
 27
 28	// Generate system prompt for this directory
 29	prompt, err := GenerateSystemPrompt(tmpDir)
 30	if err != nil {
 31		t.Fatalf("GenerateSystemPrompt failed: %v", err)
 32	}
 33
 34	// Verify the unique content from AGENTS.md is included in the prompt
 35	if !strings.Contains(prompt, "TEST_UNIQUE_CONTENT_12345") {
 36		t.Errorf("system prompt should contain content from AGENTS.md in the working directory")
 37		t.Logf("AGENTS.md content: %s", agentsContent)
 38		t.Logf("Generated prompt (first 2000 chars): %s", prompt[:min(len(prompt), 2000)])
 39	}
 40
 41	// Verify the file path is mentioned in guidance section
 42	if !strings.Contains(prompt, agentsFile) {
 43		t.Errorf("system prompt should reference the AGENTS.md file path")
 44	}
 45}
 46
 47// TestSystemPromptEmptyCwdFallsBackToCurrentDir verifies that an empty workingDir
 48// causes GenerateSystemPrompt to use the current directory.
 49func TestSystemPromptEmptyCwdFallsBackToCurrentDir(t *testing.T) {
 50	// Get current directory for comparison
 51	currentDir, err := os.Getwd()
 52	if err != nil {
 53		t.Fatalf("failed to get current directory: %v", err)
 54	}
 55
 56	// Generate system prompt with empty workingDir
 57	prompt, err := GenerateSystemPrompt("")
 58	if err != nil {
 59		t.Fatalf("GenerateSystemPrompt failed: %v", err)
 60	}
 61
 62	// Verify the current directory is mentioned in the prompt
 63	if !strings.Contains(prompt, currentDir) {
 64		t.Errorf("system prompt should contain current directory when cwd is empty")
 65	}
 66}
 67
 68// TestSystemPromptDetectsGitInWorkingDir verifies that the system prompt
 69// correctly detects a git repo in the specified working directory, not the
 70// process's cwd. Regression test for https://github.com/boldsoftware/shelley/issues/71
 71func TestSystemPromptDetectsGitInWorkingDir(t *testing.T) {
 72	// Create a temp dir with a git repo
 73	tmpDir, err := os.MkdirTemp("", "shelley_git_test")
 74	if err != nil {
 75		t.Fatalf("failed to create temp dir: %v", err)
 76	}
 77	defer os.RemoveAll(tmpDir)
 78
 79	// Initialize a git repo in the temp dir
 80	cmd := exec.Command("git", "init")
 81	cmd.Dir = tmpDir
 82	if out, err := cmd.CombinedOutput(); err != nil {
 83		t.Fatalf("git init failed: %v\n%s", err, out)
 84	}
 85	cmd = exec.Command("git", "commit", "--allow-empty", "-m", "initial")
 86	cmd.Dir = tmpDir
 87	if out, err := cmd.CombinedOutput(); err != nil {
 88		t.Fatalf("git commit failed: %v\n%s", err, out)
 89	}
 90
 91	// Generate system prompt for the git repo directory
 92	prompt, err := GenerateSystemPrompt(tmpDir)
 93	if err != nil {
 94		t.Fatalf("GenerateSystemPrompt failed: %v", err)
 95	}
 96
 97	// The prompt should say "Git repository root:" not "Not in a git repository"
 98	if strings.Contains(prompt, "Not in a git repository") {
 99		t.Errorf("system prompt incorrectly says 'Not in a git repository' for a directory that is a git repo")
100	}
101	if !strings.Contains(prompt, "Git repository root:") {
102		t.Errorf("system prompt should contain 'Git repository root:' for a git repo directory")
103	}
104	if !strings.Contains(prompt, tmpDir) {
105		t.Errorf("system prompt should reference the git root directory %s", tmpDir)
106	}
107}
108
109func min(a, b int) int {
110	if a < b {
111		return a
112	}
113	return b
114}
115
116// TestSystemPromptIncludesSkillsFromAnyWorkingDir verifies that user-level
117// skills (e.g. from ~/.config/agents/skills) appear in the system prompt
118// regardless of the conversation's working directory.
119// Regression test for https://github.com/boldsoftware/shelley/issues/83
120func TestSystemPromptIncludesSkillsFromAnyWorkingDir(t *testing.T) {
121	// Create a fake home with a skill
122	tmpHome := t.TempDir()
123	skillDir := filepath.Join(tmpHome, ".config", "agents", "skills", "test-skill")
124	if err := os.MkdirAll(skillDir, 0o755); err != nil {
125		t.Fatal(err)
126	}
127	if err := os.WriteFile(filepath.Join(skillDir, "SKILL.md"), []byte("---\nname: test-skill\ndescription: A test skill for issue 83.\n---\nInstructions.\n"), 0o644); err != nil {
128		t.Fatal(err)
129	}
130
131	oldHome := os.Getenv("HOME")
132	os.Setenv("HOME", tmpHome)
133	t.Cleanup(func() { os.Setenv("HOME", oldHome) })
134
135	// Generate system prompt from a directory completely unrelated to home
136	unrelatedDir := t.TempDir()
137	prompt, err := GenerateSystemPrompt(unrelatedDir)
138	if err != nil {
139		t.Fatalf("GenerateSystemPrompt failed: %v", err)
140	}
141
142	if !strings.Contains(prompt, "test-skill") {
143		t.Error("system prompt should contain skill 'test-skill' even when working dir is unrelated to home")
144	}
145	if !strings.Contains(prompt, "A test skill for issue 83.") {
146		t.Error("system prompt should contain the skill description")
147	}
148}