@@ -5,26 +5,12 @@ import (
55 "os"
66 "path/filepath"
77 "strings"
8+ "sync"
89
910 "github.com/opencode-ai/opencode/internal/config"
1011 "github.com/opencode-ai/opencode/internal/llm/models"
1112)
1213
13−// contextFiles is a list of potential context files to check for
14−var contextFiles = []string{
15− ".github/copilot-instructions.md",
16− ".cursorrules",
17− ".cursor/rules/", // Directory containing multiple rule files
18− "CLAUDE.md",
19− "CLAUDE.local.md",
20− "opencode.md",
21− "opencode.local.md",
22− "OpenCode.md",
23− "OpenCode.local.md",
24− "OPENCODE.md",
25− "OPENCODE.local.md",
26−}
27−
2814func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string {
2915 basePrompt := ""
3016 switch agentName {
@@ -40,45 +26,86 @@ func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) s
4026
4127 if agentName == config.AgentCoder || agentName == config.AgentTask {
4228 // Add context from project-specific instruction files if they exist
43− contextContent := getContextFromFiles()
29+ contextContent := getContextFromPaths()
4430 if contextContent != "" {
45− return fmt.Sprintf("%s\n\n# Project-Specific Context\n%s", basePrompt, contextContent)
31+ return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
4632 }
4733 }
4834 return basePrompt
4935}
5036
51−// getContextFromFiles checks for the existence of context files and returns their content
52−func getContextFromFiles() string {
53− workDir := config.WorkingDirectory()
54− var contextContent string
37+var (
38+ onceContext sync.Once
39+ contextContent string
40+)
41+
42+func getContextFromPaths() string {
43+ onceContext.Do(func() {
44+ var (
45+ cfg = config.Get()
46+ workDir = cfg.WorkingDir
47+ contextPaths = cfg.ContextPaths
48+ )
49+
50+ contextContent = processContextPaths(workDir, contextPaths)
51+ })
52+
53+ return contextContent
54+}
55+
56+func processContextPaths(workDir string, paths []string) string {
57+ var (
58+ wg sync.WaitGroup
59+ resultCh = make(chan string)
60+ )
61+
62+ for _, path := range paths {
63+ wg.Add(1)
64+ go func(p string) {
65+ defer wg.Done()
5566
56− for _, path := range contextFiles {
57− // Check if path ends with a slash (indicating a directory)
58− if strings.HasSuffix(path, "/") {
59− // Handle directory - read all files within it
60− dirPath := filepath.Join(workDir, path)
61− files, err := os.ReadDir(dirPath)
62− if err == nil {
63− for _, file := range files {
64− if !file.IsDir() {
65− filePath := filepath.Join(dirPath, file.Name())
66− content, err := os.ReadFile(filePath)
67− if err == nil {
68− contextContent += fmt.Sprintf("\n# From %s\n%s\n", file.Name(), string(content))
67+ if strings.HasSuffix(p, "/") {
68+ filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
69+ if err != nil {
70+ return err
71+ }
72+ if !d.IsDir() {
73+ if result := processFile(path); result != "" {
74+ resultCh <- result
6975 }
7076 }
77+ return nil
78+ })
79+ } else {
80+ result := processFile(filepath.Join(workDir, p))
81+ if result != "" {
82+ resultCh <- result
7183 }
7284 }
73− } else {
74− // Handle individual file as before
75− filePath := filepath.Join(workDir, path)
76− content, err := os.ReadFile(filePath)
77− if err == nil {
78− contextContent += fmt.Sprintf("\n%s\n", string(content))
79− }
80− }
85+ }(path)
8186 }
8287
83− return contextContent
88+ go func() {
89+ wg.Wait()
90+ close(resultCh)
91+ }()
92+
93+ var (
94+ results = make([]string, len(resultCh))
95+ i int
96+ )
97+ for result := range resultCh {
98+ results[i] = result
99+ i++
100+ }
101+
102+ return strings.Join(results, "\n")
84103}
104+
105+func processFile(filePath string) string {
106+ content, err := os.ReadFile(filePath)
107+ if err != nil {
108+ return ""
109+ }
110+ return "# From:" + filePath + "\n" + string(content)
111+}