feat(config): default to AGENTS.md w/ new setting (#1403)

Amolith created

Change summary

internal/agent/prompts.go                            | 14 ++++++++++----
internal/agent/templates/initialize.md.tpl           |  6 +++---
internal/config/config.go                            |  2 ++
internal/config/load.go                              |  3 +++
internal/config/load_test.go                         |  1 +
internal/tui/components/chat/splash/splash.go        |  9 +++++++--
internal/tui/components/dialogs/commands/commands.go |  9 +++++++--
internal/tui/page/chat/chat.go                       |  2 +-
8 files changed, 34 insertions(+), 12 deletions(-)

Detailed changes

internal/agent/prompts.go 🔗

@@ -1,9 +1,11 @@
 package agent
 
 import (
+	"context"
 	_ "embed"
 
 	"github.com/charmbracelet/crush/internal/agent/prompt"
+	"github.com/charmbracelet/crush/internal/config"
 )
 
 //go:embed templates/coder.md.tpl
@@ -12,8 +14,8 @@ var coderPromptTmpl []byte
 //go:embed templates/task.md.tpl
 var taskPromptTmpl []byte
 
-//go:embed templates/initialize.md
-var initializePrompt []byte
+//go:embed templates/initialize.md.tpl
+var initializePromptTmpl []byte
 
 func coderPrompt(opts ...prompt.Option) (*prompt.Prompt, error) {
 	systemPrompt, err := prompt.NewPrompt("coder", string(coderPromptTmpl), opts...)
@@ -31,6 +33,10 @@ func taskPrompt(opts ...prompt.Option) (*prompt.Prompt, error) {
 	return systemPrompt, nil
 }
 
-func InitializePrompt() string {
-	return string(initializePrompt)
+func InitializePrompt(cfg config.Config) (string, error) {
+	systemPrompt, err := prompt.NewPrompt("initialize", string(initializePromptTmpl))
+	if err != nil {
+		return "", err
+	}
+	return systemPrompt.Build(context.Background(), "", "", cfg)
 }

internal/agent/templates/initialize.md → internal/agent/templates/initialize.md.tpl 🔗

@@ -1,6 +1,6 @@
-Analyze this codebase and create/update **CRUSH.md** to help future agents work effectively in this repository.
+Analyze this codebase and create/update **{{.Config.Options.InitializeAs}}** to help future agents work effectively in this repository.
 
-**First**: Check if directory is empty or contains only config files. If so, stop and say "Directory appears empty or only contains config. Add source code first, then run this command to generate CRUSH.md."
+**First**: Check if directory is empty or contains only config files. If so, stop and say "Directory appears empty or only contains config. Add source code first, then run this command to generate {{.Config.Options.InitializeAs}}."
 
 **Goal**: Document what an agent needs to know to work in this codebase - commands, patterns, conventions, gotchas.
 
@@ -11,7 +11,7 @@ Analyze this codebase and create/update **CRUSH.md** to help future agents work
 3. Identify project type from config files and directory structure
 4. Find build/test/lint commands from config files, scripts, Makefiles, or CI configs
 5. Read representative source files to understand code patterns
-6. If CRUSH.md exists, read and improve it
+6. If {{.Config.Options.InitializeAs}} exists, read and improve it
 
 **Content to include**:
 

internal/config/config.go 🔗

@@ -21,6 +21,7 @@ import (
 const (
 	appName              = "crush"
 	defaultDataDirectory = ".crush"
+	defaultInitializeAs  = "AGENTS.md"
 )
 
 var defaultContextPaths = []string{
@@ -201,6 +202,7 @@ type Options struct {
 	DisableProviderAutoUpdate bool         `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"`
 	Attribution               *Attribution `json:"attribution,omitempty" jsonschema:"description=Attribution settings for generated content"`
 	DisableMetrics            bool         `json:"disable_metrics,omitempty" jsonschema:"description=Disable sending metrics,default=false"`
+	InitializeAs              string       `json:"initialize_as,omitempty" jsonschema:"description=Name of the context file to create/update during project initialization,default=AGENTS.md,example=AGENTS.md,example=CRUSH.md,example=CLAUDE.md,example=docs/LLMs.md"`
 }
 
 type MCPs map[string]MCPConfig

internal/config/load.go 🔗

@@ -364,6 +364,9 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
 			c.Options.Attribution.TrailerStyle = TrailerStyleCoAuthoredBy
 		}
 	}
+	if c.Options.InitializeAs == "" {
+		c.Options.InitializeAs = defaultInitializeAs
+	}
 }
 
 // applyLSPDefaults applies default values from powernap to LSP configurations

internal/config/load_test.go 🔗

@@ -50,6 +50,7 @@ func TestConfig_setDefaults(t *testing.T) {
 	require.NotNil(t, cfg.LSP)
 	require.NotNil(t, cfg.MCP)
 	require.Equal(t, filepath.Join("/tmp", ".crush"), cfg.Options.DataDirectory)
+	require.Equal(t, "AGENTS.md", cfg.Options.InitializeAs)
 	for _, path := range defaultContextPaths {
 		require.Contains(t, cfg.Options.ContextPaths, path)
 	}

internal/tui/components/chat/splash/splash.go 🔗

@@ -331,10 +331,14 @@ func (s *splashCmp) initializeProject() tea.Cmd {
 
 	cmds = append(cmds, util.CmdHandler(OnboardingCompleteMsg{}))
 	if !s.selectedNo {
+		initPrompt, err := agent.InitializePrompt(*config.Get())
+		if err != nil {
+			return util.ReportError(err)
+		}
 		cmds = append(cmds,
 			util.CmdHandler(chat.SessionClearedMsg{}),
 			util.CmdHandler(chat.SendMsg{
-				Text: agent.InitializePrompt(),
+				Text: initPrompt,
 			}),
 		)
 	}
@@ -458,6 +462,7 @@ func (s *splashCmp) View() string {
 		bodyStyle := t.S().Base.Foreground(t.FgMuted)
 		shortcutStyle := t.S().Base.Foreground(t.Success)
 
+		initFile := config.Get().Options.InitializeAs
 		initText := lipgloss.JoinVertical(
 			lipgloss.Left,
 			titleStyle.Render("Would you like to initialize this project?"),
@@ -465,7 +470,7 @@ func (s *splashCmp) View() string {
 			pathStyle.Render(s.cwd()),
 			"",
 			bodyStyle.Render("When I initialize your codebase I examine the project and put the"),
-			bodyStyle.Render("result into a CRUSH.md file which serves as general context."),
+			bodyStyle.Render(fmt.Sprintf("result into an %s file which serves as general context.", initFile)),
 			"",
 			bodyStyle.Render("You can also initialize anytime via ")+shortcutStyle.Render("ctrl+p")+bodyStyle.Render("."),
 			"",

internal/tui/components/dialogs/commands/commands.go 🔗

@@ -1,6 +1,7 @@
 package commands
 
 import (
+	"fmt"
 	"os"
 	"slices"
 	"strings"
@@ -454,10 +455,14 @@ func (c *commandDialogCmp) defaultCommands() []Command {
 		{
 			ID:          "init",
 			Title:       "Initialize Project",
-			Description: "Create/Update the CRUSH.md memory file",
+			Description: fmt.Sprintf("Create/Update the %s memory file", config.Get().Options.InitializeAs),
 			Handler: func(cmd Command) tea.Cmd {
+				initPrompt, err := agent.InitializePrompt(*config.Get())
+				if err != nil {
+					return util.ReportError(err)
+				}
 				return util.CmdHandler(chat.SendMsg{
-					Text: agent.InitializePrompt(),
+					Text: initPrompt,
 				})
 			},
 		},

internal/tui/page/chat/chat.go 🔗

@@ -144,7 +144,7 @@ func (p *chatPage) Init() tea.Cmd {
 		p.isOnboarding = true
 		p.splashFullScreen = true
 	} else if b, _ := config.ProjectNeedsInitialization(); b {
-		// Project needs CRUSH.md initialization
+		// Project needs context initialization
 		p.splash.SetProjectInit(true)
 		p.isProjectInit = true
 		p.splashFullScreen = true