From 59d95f1453ae8be23c6bd6df94a6ed69c1ea79b5 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 ca204d7205c150e5faac9c1b7c9f02427d278906..5cc2acc41764a3f759ca4cbc6bcaaf524726b324 100644 --- a/internal/agent/templates/coder.md.tpl +++ b/internal/agent/templates/coder.md.tpl @@ -339,11 +339,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 91f6a3554cf442c2edb8336def936237cf54cc35..6356dad371d0cab57df81c30adc785289bb3d530 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -321,7 +321,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 @@ -353,9 +354,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)