From e4992e7dac6490aa1a85a9d131f7bac97b4d593a Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 17 Sep 2025 20:11:15 -0600 Subject: [PATCH] feat(prompt): distinguish user/project context Separate user-defined memory paths from project-specific context paths. Previously, memory paths were appended to context paths. Now, they are passed distinctly to the prompt generator. The CoderPrompt now formats project context within tags and user memory within tags, each with a specific explanatory header for the LLM. This allows for clearer separation and potential prioritization by the model. Issue: charmbracelet/crush#1050 --- internal/agent/prompt/prompt.go | 25 ++++++++++++++++++++----- internal/agent/templates/coder.md.tpl | 18 ++++++++++++++++-- internal/config/load.go | 6 ++---- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/internal/agent/prompt/prompt.go b/internal/agent/prompt/prompt.go index d10fbcae3c3a37f295ec9f9de637cb130d9b6abc..216e7d32c0aebd4415b75885bb5bc523dae499db 100644 --- a/internal/agent/prompt/prompt.go +++ b/internal/agent/prompt/prompt.go @@ -35,6 +35,7 @@ type PromptDat struct { Date string GitStatus string ContextFiles []ContextFile + MemoryFiles []ContextFile } type ContextFile struct { @@ -150,16 +151,27 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con workingDir := cmp.Or(p.workingDir, cfg.WorkingDir()) platform := cmp.Or(p.platform, runtime.GOOS) - files := map[string][]ContextFile{} + contextFiles := map[string][]ContextFile{} + memoryFiles := map[string][]ContextFile{} for _, pth := range cfg.Options.ContextPaths { expanded := expandPath(pth, cfg) pathKey := strings.ToLower(expanded) - if _, ok := files[pathKey]; ok { + if _, ok := contextFiles[pathKey]; ok { continue } content := processContextPath(expanded, cfg) - files[pathKey] = content + contextFiles[pathKey] = content + } + + for _, pth := range cfg.Options.MemoryPaths { + expanded := expandPath(pth, cfg) + pathKey := strings.ToLower(expanded) + if _, ok := memoryFiles[pathKey]; ok { + continue + } + content := processContextPath(expanded, cfg) + memoryFiles[pathKey] = content } isGit := isGitRepo(cfg.WorkingDir()) @@ -180,8 +192,11 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, cfg con } } - for _, contextFiles := range files { - data.ContextFiles = append(data.ContextFiles, contextFiles...) + for _, files := range contextFiles { + data.ContextFiles = append(data.ContextFiles, files...) + } + for _, files := range memoryFiles { + data.MemoryFiles = append(data.MemoryFiles, files...) } return data, nil } diff --git a/internal/agent/templates/coder.md.tpl b/internal/agent/templates/coder.md.tpl index 225e021efe5f08f2cfd68184d684af2cbd57684e..385e146f0942260ae25a35ebd20642e178bd600f 100644 --- a/internal/agent/templates/coder.md.tpl +++ b/internal/agent/templates/coder.md.tpl @@ -362,11 +362,25 @@ Diagnostics (lint/typecheck) included in tool output. {{end}} {{if .ContextFiles}} - +# Project-Specific Context +Make sure to follow the instructions in the context below. + {{range .ContextFiles}} {{.Content}} {{end}} - + +{{end}} + +{{if .MemoryFiles}} +# User context +The following is personal content added by the user that they'd like you to follow no matter what project you're working in. + +{{range .MemoryFiles}} + +{{.Content}} + +{{end}} + {{end}} diff --git a/internal/config/load.go b/internal/config/load.go index d46ac4638f41356d1820b58661eb26f150fb922c..a8f2522a4e2caf55cd08665311bd3b27d8bba0ee 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -349,7 +349,8 @@ func (c *Config) setDefaults(workingDir, dataDir string) { filepath.Join(filepath.Dir(crushConfigDir), "AGENTS.md"), } } - c.Options.ContextPaths = append(c.Options.ContextPaths, c.Options.MemoryPaths...) + slices.Sort(c.Options.MemoryPaths) + c.Options.MemoryPaths = slices.Compact(c.Options.MemoryPaths) if dataDir != "" { c.Options.DataDirectory = dataDir @@ -381,9 +382,6 @@ func (c *Config) setDefaults(workingDir, dataDir string) { // Add the default context paths if they are not already present c.Options.ContextPaths = append(defaultContextPaths, c.Options.ContextPaths...) - - // The ordering of contexts can be important; maybe the user's stuff should - // go at the bottom? Or top? slices.Sort(c.Options.ContextPaths) c.Options.ContextPaths = slices.Compact(c.Options.ContextPaths)