on main help sign in
feat(config): load user-level context files

Load ~/.config/crush/CRUSH.md and ~/.config/AGENTS.md by default as global context files, separate from project context. Global context is presented to the model as user preferences that apply across all projects. Paths are configurable via global_context_paths.

commit6242e4f47743190fe60a27ba881ba3d6a9b36deb authorAmolith <amolith@secluded.site> dateWed 27 May 2026, 14:13 (5 months ago) parentaf7cab7
7 files changed+97−23
README.md+27 internal/agent/common_test.go+1 internal/agent/prompt/prompt.go+30−21 internal/agent/templates/coder.md.tpl+16−2 internal/config/config.go+1 internal/config/load.go+11 schema.json+11
README.md+27
@@ -402,6 +402,33 @@ down. There is a short grace window right after `POST /v1/workspaces` so a
402402client that has created the workspace but not yet opened its event stream
403403does not get reaped before it can attach.
404404
405+### Global context files
406+
407+Crush automatically includes two files for cross-project instructions.
408+
409+- `~/.config/crush/CRUSH.md`: Crush-specific rules that would confuse other
410+ agentic coding tools. If you only use Crush, this is the only one you need to
411+ edit.
412+- `~/.config/AGENTS.md`: generic instructions that other coding tools might
413+ read. Avoid referring to Crush-specific features or workflows here. You
414+ probably only care about this if you use multiple agentic coding tools and
415+ want to share instructions between them.
416+
417+You can customize these paths using the `global_context_paths` option in your
418+configuration:
419+
420+```jsonc
421+{
422+ "$schema": "https://charm.land/crush.json",
423+ "options": {
424+ "global_context_paths": [
425+ "~/path/to/custom/context/file.md",
426+ "/full/path/to/folder/of/files/" // recursively load all .md files in folder
427+ ]
428+ }
429+}
430+```
431+
405432### Ignoring Files
406433
407434Crush respects `.gitignore` files by default, but you can also create a
internal/agent/common_test.go+1
@@ -152,6 +152,7 @@ func coderAgent(r *vcr.Recorder, env fakeEnv, large, small fantasy.LanguageModel
152152 cfg.Config().Options.SkillsPaths = nil
153153 cfg.Config().Options.DisabledSkills = []string{"crush-config"}
154154 cfg.Config().Options.ContextPaths = nil
155+ cfg.Config().Options.GlobalContextPaths = nil
155156 cfg.Config().LSP = nil
156157
157158 systemPrompt, err := prompt.Build(context.TODO(), large.Provider(), large.Model(), cfg)
internal/agent/prompt/prompt.go+30−21
@@ -29,16 +29,17 @@ type Prompt struct {
2929}
3030
3131type PromptDat struct {
32− Provider string
33− Model string
34− Config config.Config
35− WorkingDir string
36− IsGitRepo bool
37− Platform string
38− Date string
39− GitStatus string
40− ContextFiles []ContextFile
41− AvailSkillXML string
32+ Provider string
33+ Model string
34+ Config config.Config
35+ WorkingDir string
36+ IsGitRepo bool
37+ Platform string
38+ Date string
39+ GitStatus string
40+ ContextFiles []ContextFile
41+ GlobalContextFiles []ContextFile
42+ AvailSkillXML string
4243}
4344
4445type ContextFile struct {
@@ -147,22 +148,27 @@ func expandPath(path string, store *config.ConfigStore) string {
147148 return path
148149}
149150
150−func (p *Prompt) promptData(ctx context.Context, provider, model string, store *config.ConfigStore) (PromptDat, error) {
151− workingDir := cmp.Or(p.workingDir, store.WorkingDir())
152− platform := cmp.Or(p.platform, runtime.GOOS)
153−
151+// loadContextFiles loads and deduplicates context files from a list of paths.
152+func loadContextFiles(paths []string, store *config.ConfigStore) map[string][]ContextFile {
154153 files := map[string][]ContextFile{}
155−
156− cfg := store.Config()
157− for _, pth := range cfg.Options.ContextPaths {
154+ for _, pth := range paths {
158155 expanded := expandPath(pth, store)
159156 pathKey := strings.ToLower(expanded)
160157 if _, ok := files[pathKey]; ok {
161158 continue
162159 }
163− content := processContextPath(expanded, store)
164− files[pathKey] = content
160+ files[pathKey] = processContextPath(expanded, store)
165161 }
162+ return files
163+}
164+
165+func (p *Prompt) promptData(ctx context.Context, provider, model string, store *config.ConfigStore) (PromptDat, error) {
166+ workingDir := cmp.Or(p.workingDir, store.WorkingDir())
167+ platform := cmp.Or(p.platform, runtime.GOOS)
168+
169+ cfg := store.Config()
170+ contextFiles := loadContextFiles(cfg.Options.ContextPaths, store)
171+ globalContextFiles := loadContextFiles(cfg.Options.GlobalContextPaths, store)
166172
167173 // Discover and load skills metadata.
168174 var availSkillXML string
@@ -217,8 +223,11 @@ func (p *Prompt) promptData(ctx context.Context, provider, model string, store *
217223 }
218224 }
219225
220− for _, contextFiles := range files {
221− data.ContextFiles = append(data.ContextFiles, contextFiles...)
226+ for _, files := range contextFiles {
227+ data.ContextFiles = append(data.ContextFiles, files...)
228+ }
229+ for _, files := range globalContextFiles {
230+ data.GlobalContextFiles = append(data.GlobalContextFiles, files...)
222231 }
223232 return data, nil
224233}
internal/agent/templates/coder.md.tpl+16−2
@@ -396,11 +396,25 @@ If a skill mentions scripts, references, or assets, they live in the same folder
396396{{end}}
397397
398398{{if .ContextFiles}}
399−<memory>
399+# Project-Specific Context
400+Make sure to follow the instructions in the context below.
401+<project_context>
400402{{range .ContextFiles}}
401403<file path="{{.Path}}">
402404{{.Content}}
403405</file>
404406{{end}}
405−</memory>
407+</project_context>
408+{{end}}
409+{{if .GlobalContextFiles}}
410+
411+# User context
412+The following is personal content added by the user that they'd like you to follow no matter what project you're working in.
413+<user_preferences>
414+{{range .GlobalContextFiles}}
415+<file path="{{.Path}}">
416+{{.Content}}
417+</file>
418+{{end}}
419+</user_preferences>
406420{{end}}
internal/config/config.go+1
@@ -260,6 +260,7 @@ func (Attribution) JSONSchemaExtend(schema *jsonschema.Schema) {
260260
261261type Options struct {
262262 ContextPaths []string `json:"context_paths,omitempty" jsonschema:"description=Paths to files containing context information for the AI,example=.cursorrules,example=CRUSH.md"`
263+ GlobalContextPaths []string `json:"global_context_paths,omitempty" jsonschema:"description=Paths to files containing global context information for the AI,default=~/.config/crush/CRUSH.md,default=~/.config/AGENTS.md"`
263264 SkillsPaths []string `json:"skills_paths,omitempty" jsonschema:"description=Paths to directories containing Agent Skills (folders with SKILL.md files),example=~/.config/crush/skills,example=./skills"`
264265 TUI *TUIOptions `json:"tui,omitempty" jsonschema:"description=Terminal user interface options"`
265266 Debug bool `json:"debug,omitempty" jsonschema:"description=Enable debug logging,default=false"`
internal/config/load.go+11
@@ -417,6 +417,16 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
417417 if c.Options.TUI == nil {
418418 c.Options.TUI = &TUIOptions{}
419419 }
420+ if len(c.Options.GlobalContextPaths) == 0 {
421+ crushConfigDir := filepath.Dir(GlobalConfig())
422+ c.Options.GlobalContextPaths = []string{
423+ filepath.Join(crushConfigDir, "CRUSH.md"),
424+ filepath.Join(filepath.Dir(crushConfigDir), "AGENTS.md"),
425+ }
426+ }
427+ slices.Sort(c.Options.GlobalContextPaths)
428+ c.Options.GlobalContextPaths = slices.Compact(c.Options.GlobalContextPaths)
429+
420430 if dataDir != "" {
421431 c.Options.DataDirectory = dataDir
422432 } else if c.Options.DataDirectory == "" {
@@ -448,6 +458,7 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
448458
449459 // Add the default context paths if they are not already present
450460 c.Options.ContextPaths = append(slices.Clone(defaultContextPaths), c.Options.ContextPaths...)
461+
451462 slices.Sort(c.Options.ContextPaths)
452463 c.Options.ContextPaths = slices.Compact(c.Options.ContextPaths)
453464
schema.json+11
@@ -405,6 +405,17 @@
405405 "type": "array",
406406 "description": "Paths to files containing context information for the AI"
407407 },
408+ "global_context_paths": {
409+ "items": {
410+ "type": "string"
411+ },
412+ "type": "array",
413+ "description": "Paths to files containing global context information for the AI",
414+ "default": [
415+ "~/.config/crush/CRUSH.md",
416+ "~/.config/AGENTS.md"
417+ ]
418+ },
408419 "skills_paths": {
409420 "items": {
410421 "type": "string",