diff --git a/README.md b/README.md
index e25c99a5cb84372414d68a63a511eba824ac9b76..8afa0af2b517505a662562b4db4e801db0b54d2f 100644
--- a/README.md
+++ b/README.md
@@ -303,6 +303,31 @@ using `$(echo $VAR)` syntax.
}
```
+### Memory
+
+Crush automatically includes two memory files for cross-project instructions.
+
+- `~/.config/crush/CRUSH.md`: Crush-specific rules that would confuse other
+ agentic coding tools. If you only use Crush, this is the only one you need to
+ edit.
+- `~/.config/AGENTS.md`: generic instructions that other coding tools might
+ read. Avoid referring to Crush-specific tools or workflows here.
+
+You can customize these paths using the `memory_paths` option in your
+configuration:
+
+```json
+{
+ "$schema": "https://charm.land/crush.json",
+ "options": {
+ "memory_paths": [
+ "/path/to/custom/memory/file.md",
+ "/path/to/folder/of/files/" // recursively load all .md files in folder
+ ]
+ }
+}
+```
+
### Ignoring Files
Crush respects `.gitignore` files by default, but you can also create a
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/config.go b/internal/config/config.go
index b8f1fcd0dbbef7e5d5d70e2c99515db6d1f6d7b5..f7bcb30c9fd148917cb118733ab3803e61552dd1 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -217,6 +217,7 @@ func (Attribution) JSONSchemaExtend(schema *jsonschema.Schema) {
type Options struct {
ContextPaths []string `json:"context_paths,omitempty" jsonschema:"description=Paths to files containing context information for the AI,example=.cursorrules,example=CRUSH.md"`
+ MemoryPaths []string `json:"memory_paths,omitempty" jsonschema:"description=Paths to files containing memory information for the AI,default=~/.config/crush/CRUSH.md,default=~/.config/AGENTS.md"`
TUI *TUIOptions `json:"tui,omitempty" jsonschema:"description=Terminal user interface options"`
Debug bool `json:"debug,omitempty" jsonschema:"description=Enable debug logging,default=false"`
DebugLSP bool `json:"debug_lsp,omitempty" jsonschema:"description=Enable debug logging for LSP servers,default=false"`
diff --git a/internal/config/load.go b/internal/config/load.go
index 14dd0f8792bcbabaa865efe47e0db5e721cf3827..ca2775d9b040b61fc0cede6126b9ca9843de35a0 100644
--- a/internal/config/load.go
+++ b/internal/config/load.go
@@ -322,6 +322,16 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
if c.Options.ContextPaths == nil {
c.Options.ContextPaths = []string{}
}
+ if c.Options.MemoryPaths == nil {
+ crushConfigDir := filepath.Dir(GlobalConfig())
+ c.Options.MemoryPaths = []string{
+ filepath.Join(crushConfigDir, "CRUSH.md"),
+ filepath.Join(filepath.Dir(crushConfigDir), "AGENTS.md"),
+ }
+ }
+ slices.Sort(c.Options.MemoryPaths)
+ c.Options.MemoryPaths = slices.Compact(c.Options.MemoryPaths)
+
if dataDir != "" {
c.Options.DataDirectory = dataDir
} else if c.Options.DataDirectory == "" {
diff --git a/schema.json b/schema.json
index 974200854d28bb300c94613328485ea5a3e2165d..22660da0e4d52849bde30273c3346ca63f577975 100644
--- a/schema.json
+++ b/schema.json
@@ -367,6 +367,17 @@
"type": "array",
"description": "Paths to files containing context information for the AI"
},
+ "memory_paths": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array",
+ "description": "Paths to files containing memory information for the AI",
+ "default": [
+ "~/.config/crush/CRUSH.md",
+ "~/.config/AGENTS.md"
+ ]
+ },
"tui": {
"$ref": "#/$defs/TUIOptions",
"description": "Terminal user interface options"