1package prompt
2
3import (
4 "context"
5 _ "embed"
6 "fmt"
7 "log/slog"
8 "os"
9 "path/filepath"
10 "runtime"
11 "time"
12
13 "github.com/charmbracelet/catwalk/pkg/catwalk"
14 "github.com/charmbracelet/crush/internal/config"
15 "github.com/charmbracelet/crush/internal/llm/tools"
16)
17
18func CoderPrompt(p string, contextFiles ...string) string {
19 var basePrompt string
20
21 if os.Getenv("CRUSH_CODER_V2") == "true" {
22 basePrompt = string(baseCoderV2Prompt)
23 } else {
24 switch p {
25 case string(catwalk.InferenceProviderOpenAI):
26 basePrompt = string(baseOpenAICoderPrompt)
27 case string(catwalk.InferenceProviderGemini), string(catwalk.InferenceProviderVertexAI):
28 basePrompt = string(baseGeminiCoderPrompt)
29 default:
30 basePrompt = string(baseAnthropicCoderPrompt)
31 }
32 }
33 envInfo := getEnvironmentInfo()
34
35 basePrompt = fmt.Sprintf("%s\n\n%s\n%s", basePrompt, envInfo, lspInformation())
36
37 contextContent := getContextFromPaths(config.Get().WorkingDir(), contextFiles)
38 slog.Debug("Context content", "Context", contextContent)
39 if contextContent != "" {
40 return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
41 }
42 return basePrompt
43}
44
45//go:embed v2.md
46var baseCoderV2Prompt []byte
47
48//go:embed openai.md
49var baseOpenAICoderPrompt []byte
50
51//go:embed anthropic.md
52var baseAnthropicCoderPrompt []byte
53
54//go:embed gemini.md
55var baseGeminiCoderPrompt []byte
56
57func getEnvironmentInfo() string {
58 cwd := config.Get().WorkingDir()
59 isGit := isGitRepo(cwd)
60 platform := runtime.GOOS
61 date := time.Now().Format("1/2/2006")
62 ls := tools.NewLsTool(cwd)
63 r, _ := ls.Run(context.Background(), tools.ToolCall{
64 Input: `{"path":"."}`,
65 })
66 return fmt.Sprintf(`Here is useful information about the environment you are running in:
67<env>
68Working directory: %s
69Is directory a git repo: %s
70Platform: %s
71Today's date: %s
72</env>
73<project>
74%s
75</project>
76 `, cwd, boolToYesNo(isGit), platform, date, r.Content)
77}
78
79func isGitRepo(dir string) bool {
80 _, err := os.Stat(filepath.Join(dir, ".git"))
81 return err == nil
82}
83
84func lspInformation() string {
85 cfg := config.Get()
86 hasLSP := false
87 for _, v := range cfg.LSP {
88 if !v.Disabled {
89 hasLSP = true
90 break
91 }
92 }
93 if !hasLSP {
94 return ""
95 }
96 return `# LSP Information
97Tools that support it will also include useful diagnostics such as linting and typechecking.
98- These diagnostics will be automatically enabled when you run the tool, and will be displayed in the output at the bottom within the <file_diagnostics></file_diagnostics> and <project_diagnostics></project_diagnostics> tags.
99- Take necessary actions to fix the issues.
100- You should ignore diagnostics of files that you did not change or are not related or caused by your changes unless the user explicitly asks you to fix them.
101`
102}
103
104func boolToYesNo(b bool) string {
105 if b {
106 return "Yes"
107 }
108 return "No"
109}