summaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorGarrett Ladley <[email protected]>2025-04-27 14:11:09 -0400
committerGitHub <[email protected]>2025-04-27 20:11:09 +0200
commit8f3a94df92ce783f8957aeb66b08801be736adfb (patch)
treec2e9442f2e9b3df2f0e60c2220502aa1f64cb1bf /internal
parent4415220555d8de0f28a2c17c01805eb98df395d4 (diff)
downloadopencode-8f3a94df92ce783f8957aeb66b08801be736adfb.tar.gz
opencode-8f3a94df92ce783f8957aeb66b08801be736adfb.zip
feat: configure context paths (#86)
Diffstat (limited to 'internal')
-rw-r--r--internal/config/config.go32
-rw-r--r--internal/llm/prompt/prompt.go113
2 files changed, 94 insertions, 51 deletions
diff --git a/internal/config/config.go b/internal/config/config.go
index 1da1f6c94..4864ef18a 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -67,14 +67,15 @@ type LSPConfig struct {
// Config is the main configuration structure for the application.
type Config struct {
- Data Data `json:"data"`
- WorkingDir string `json:"wd,omitempty"`
- MCPServers map[string]MCPServer `json:"mcpServers,omitempty"`
- Providers map[models.ModelProvider]Provider `json:"providers,omitempty"`
- LSP map[string]LSPConfig `json:"lsp,omitempty"`
- Agents map[AgentName]Agent `json:"agents"`
- Debug bool `json:"debug,omitempty"`
- DebugLSP bool `json:"debugLSP,omitempty"`
+ Data Data `json:"data"`
+ WorkingDir string `json:"wd,omitempty"`
+ MCPServers map[string]MCPServer `json:"mcpServers,omitempty"`
+ Providers map[models.ModelProvider]Provider `json:"providers,omitempty"`
+ LSP map[string]LSPConfig `json:"lsp,omitempty"`
+ Agents map[AgentName]Agent `json:"agents"`
+ Debug bool `json:"debug,omitempty"`
+ DebugLSP bool `json:"debugLSP,omitempty"`
+ ContextPaths []string `json:"contextPaths,omitempty"`
}
// Application constants
@@ -84,6 +85,20 @@ const (
appName = "opencode"
)
+var defaultContextPaths = []string{
+ ".github/copilot-instructions.md",
+ ".cursorrules",
+ ".cursor/rules/",
+ "CLAUDE.md",
+ "CLAUDE.local.md",
+ "opencode.md",
+ "opencode.local.md",
+ "OpenCode.md",
+ "OpenCode.local.md",
+ "OPENCODE.md",
+ "OPENCODE.local.md",
+}
+
// Global configuration instance
var cfg *Config
@@ -185,6 +200,7 @@ func configureViper() {
// setDefaults configures default values for configuration options.
func setDefaults(debug bool) {
viper.SetDefault("data.directory", defaultDataDirectory)
+ viper.SetDefault("contextPaths", defaultContextPaths)
if debug {
viper.SetDefault("debug", true)
diff --git a/internal/llm/prompt/prompt.go b/internal/llm/prompt/prompt.go
index 32971f60e..7290ed9a5 100644
--- a/internal/llm/prompt/prompt.go
+++ b/internal/llm/prompt/prompt.go
@@ -5,26 +5,12 @@ import (
"os"
"path/filepath"
"strings"
+ "sync"
"github.com/opencode-ai/opencode/internal/config"
"github.com/opencode-ai/opencode/internal/llm/models"
)
-// contextFiles is a list of potential context files to check for
-var contextFiles = []string{
- ".github/copilot-instructions.md",
- ".cursorrules",
- ".cursor/rules/", // Directory containing multiple rule files
- "CLAUDE.md",
- "CLAUDE.local.md",
- "opencode.md",
- "opencode.local.md",
- "OpenCode.md",
- "OpenCode.local.md",
- "OPENCODE.md",
- "OPENCODE.local.md",
-}
-
func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) string {
basePrompt := ""
switch agentName {
@@ -40,45 +26,86 @@ func GetAgentPrompt(agentName config.AgentName, provider models.ModelProvider) s
if agentName == config.AgentCoder || agentName == config.AgentTask {
// Add context from project-specific instruction files if they exist
- contextContent := getContextFromFiles()
+ contextContent := getContextFromPaths()
if contextContent != "" {
- return fmt.Sprintf("%s\n\n# Project-Specific Context\n%s", basePrompt, contextContent)
+ return fmt.Sprintf("%s\n\n# Project-Specific Context\n Make sure to follow the instructions in the context below\n%s", basePrompt, contextContent)
}
}
return basePrompt
}
-// getContextFromFiles checks for the existence of context files and returns their content
-func getContextFromFiles() string {
- workDir := config.WorkingDirectory()
- var contextContent string
+var (
+ onceContext sync.Once
+ contextContent string
+)
+
+func getContextFromPaths() string {
+ onceContext.Do(func() {
+ var (
+ cfg = config.Get()
+ workDir = cfg.WorkingDir
+ contextPaths = cfg.ContextPaths
+ )
+
+ contextContent = processContextPaths(workDir, contextPaths)
+ })
+
+ return contextContent
+}
+
+func processContextPaths(workDir string, paths []string) string {
+ var (
+ wg sync.WaitGroup
+ resultCh = make(chan string)
+ )
+
+ for _, path := range paths {
+ wg.Add(1)
+ go func(p string) {
+ defer wg.Done()
- for _, path := range contextFiles {
- // Check if path ends with a slash (indicating a directory)
- if strings.HasSuffix(path, "/") {
- // Handle directory - read all files within it
- dirPath := filepath.Join(workDir, path)
- files, err := os.ReadDir(dirPath)
- if err == nil {
- for _, file := range files {
- if !file.IsDir() {
- filePath := filepath.Join(dirPath, file.Name())
- content, err := os.ReadFile(filePath)
- if err == nil {
- contextContent += fmt.Sprintf("\n# From %s\n%s\n", file.Name(), string(content))
+ if strings.HasSuffix(p, "/") {
+ filepath.WalkDir(filepath.Join(workDir, p), func(path string, d os.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if !d.IsDir() {
+ if result := processFile(path); result != "" {
+ resultCh <- result
}
}
+ return nil
+ })
+ } else {
+ result := processFile(filepath.Join(workDir, p))
+ if result != "" {
+ resultCh <- result
}
}
- } else {
- // Handle individual file as before
- filePath := filepath.Join(workDir, path)
- content, err := os.ReadFile(filePath)
- if err == nil {
- contextContent += fmt.Sprintf("\n%s\n", string(content))
- }
- }
+ }(path)
}
- return contextContent
+ go func() {
+ wg.Wait()
+ close(resultCh)
+ }()
+
+ var (
+ results = make([]string, len(resultCh))
+ i int
+ )
+ for result := range resultCh {
+ results[i] = result
+ i++
+ }
+
+ return strings.Join(results, "\n")
}
+
+func processFile(filePath string) string {
+ content, err := os.ReadFile(filePath)
+ if err != nil {
+ return ""
+ }
+ return "# From:" + filePath + "\n" + string(content)
+} \ No newline at end of file