feat: configure context paths (#86)

This commit is contained in:
Garrett Ladley 2025-04-27 14:11:09 -04:00 committed by GitHub
parent 4415220555
commit 8f3a94df92
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 185 additions and 102 deletions

View file

@ -77,6 +77,27 @@ func generateSchema() map[string]any {
"default": false,
}
schema["properties"].(map[string]any)["contextPaths"] = map[string]any{
"type": "array",
"description": "Context paths for the application",
"items": map[string]any{
"type": "string",
},
"default": []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",
},
}
// Add MCP servers
schema["properties"].(map[string]any)["mcpServers"] = map[string]any{
"type": "object",
@ -259,4 +280,3 @@ func generateSchema() map[string]any {
return schema
}

View file

@ -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)

View file

@ -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
)
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))
}
}
}
}
} 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))
}
}
}
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()
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
}
}
}(path)
}
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)
}

View file

@ -12,33 +12,33 @@
"model": {
"description": "Model ID for the agent",
"enum": [
"claude-3.7-sonnet",
"claude-3-opus",
"gpt-4.1-mini",
"gpt-4o",
"gpt-4o-mini",
"gemini-2.0-flash-lite",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"gpt-4.1",
"gpt-4.5-preview",
"o1",
"gpt-4.1-nano",
"o3-mini",
"gemini-2.5-flash",
"gemini-2.0-flash",
"meta-llama/llama-4-scout-17b-16e-instruct",
"bedrock.claude-3.7-sonnet",
"o1-pro",
"o3",
"gemini-2.5",
"qwen-qwq",
"llama-3.3-70b-versatile",
"deepseek-r1-distill-llama-70b",
"claude-3.5-sonnet",
"claude-3-haiku",
"claude-3.7-sonnet",
"claude-3.5-haiku",
"o3",
"gpt-4.5-preview",
"o1-pro",
"o4-mini",
"o1-mini"
"gpt-4.1",
"o3-mini",
"gpt-4.1-nano",
"gpt-4o-mini",
"o1",
"gemini-2.5-flash",
"qwen-qwq",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"claude-3-opus",
"gpt-4o",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"deepseek-r1-distill-llama-70b",
"llama-3.3-70b-versatile",
"claude-3.5-sonnet",
"o1-mini",
"gpt-4.1-mini",
"gemini-2.5",
"meta-llama/llama-4-scout-17b-16e-instruct"
],
"type": "string"
},
@ -72,33 +72,33 @@
"model": {
"description": "Model ID for the agent",
"enum": [
"claude-3.7-sonnet",
"claude-3-opus",
"gpt-4.1-mini",
"gpt-4o",
"gpt-4o-mini",
"gemini-2.0-flash-lite",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"gpt-4.1",
"gpt-4.5-preview",
"o1",
"gpt-4.1-nano",
"o3-mini",
"gemini-2.5-flash",
"gemini-2.0-flash",
"meta-llama/llama-4-scout-17b-16e-instruct",
"bedrock.claude-3.7-sonnet",
"o1-pro",
"o3",
"gemini-2.5",
"qwen-qwq",
"llama-3.3-70b-versatile",
"deepseek-r1-distill-llama-70b",
"claude-3.5-sonnet",
"claude-3-haiku",
"claude-3.7-sonnet",
"claude-3.5-haiku",
"o3",
"gpt-4.5-preview",
"o1-pro",
"o4-mini",
"o1-mini"
"gpt-4.1",
"o3-mini",
"gpt-4.1-nano",
"gpt-4o-mini",
"o1",
"gemini-2.5-flash",
"qwen-qwq",
"meta-llama/llama-4-maverick-17b-128e-instruct",
"claude-3-opus",
"gpt-4o",
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"deepseek-r1-distill-llama-70b",
"llama-3.3-70b-versatile",
"claude-3.5-sonnet",
"o1-mini",
"gpt-4.1-mini",
"gemini-2.5",
"meta-llama/llama-4-scout-17b-16e-instruct"
],
"type": "string"
},
@ -131,6 +131,26 @@
},
"type": "object"
},
"contextPaths": {
"default": [
".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"
],
"description": "Context paths for the application",
"items": {
"type": "string"
},
"type": "array"
},
"data": {
"description": "Storage configuration",
"properties": {