From 2bb094881bfdf7a2a4240a76794e2a88f75455c1 Mon Sep 17 00:00:00 2001 From: Amolith Date: Fri, 15 Aug 2025 17:57:39 -0600 Subject: [PATCH] feat(config): default to AGENTS.md w/ new setting Adds new initialize_as config field allowing users to customize the name of the file created during project initialization. Defaults to AGENTS.md but can be set to CRUSH.md, CLAUDE.md, or any custom path like docs/LLMs.md. Changes: - Add InitializeAs field to Options struct with JSON schema examples - Set default value in setDefaults() following existing patterns - Convert initialize.md to template using config variable - Update InitializePrompt() to render template with config - Update UI text in splash screen and commands dialog to be dynamic - Add test coverage for default value Fixes: #843 References: #912 Assisted-by: GLM 4.6 via Crush Assisted-by: Claude Sonnet 4.5 via Crush --- internal/agent/prompts.go | 14 ++++++++++---- .../templates/{initialize.md => 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 +++++++-- .../tui/components/dialogs/commands/commands.go | 9 +++++++-- internal/tui/page/chat/chat.go | 2 +- 8 files changed, 34 insertions(+), 12 deletions(-) rename internal/agent/templates/{initialize.md => initialize.md.tpl} (85%) diff --git a/internal/agent/prompts.go b/internal/agent/prompts.go index 01be6561a9e74c71eba78991f942c77a68d8d879..577d32e4e274d9cb8274bd862af583208a613f08 100644 --- a/internal/agent/prompts.go +++ b/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) } diff --git a/internal/agent/templates/initialize.md b/internal/agent/templates/initialize.md.tpl similarity index 85% rename from internal/agent/templates/initialize.md rename to internal/agent/templates/initialize.md.tpl index 5eb1636c44094982ac1784aadeabdecb022a9dcc..1ca1a8132b07bc9d05efe32aaea04d050a1a1a2e 100644 --- a/internal/agent/templates/initialize.md +++ b/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**: diff --git a/internal/config/config.go b/internal/config/config.go index cb4986fe531ccf364f8dde9f73d8aa64a82a8577..7586a949a124959ed53bd95f8304ace0a8822b73 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -21,6 +21,7 @@ import ( const ( appName = "crush" defaultDataDirectory = ".crush" + defaultInitializeAs = "AGENTS.md" ) var defaultContextPaths = []string{ @@ -202,6 +203,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 diff --git a/internal/config/load.go b/internal/config/load.go index a7be6b7e2b0846229dd685b13a06580193b48792..549b8b550c2fac5338293c71a185ca07836751c9 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -374,6 +374,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 diff --git a/internal/config/load_test.go b/internal/config/load_test.go index 2882e4c521a31b2131fb2b3614589925ce8faef8..af50daad6719249815927cad70cb4900d2bd12f6 100644 --- a/internal/config/load_test.go +++ b/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) } diff --git a/internal/tui/components/chat/splash/splash.go b/internal/tui/components/chat/splash/splash.go index 3b995ab22ded328c49bda36714438c7d4c7b39eb..78d12a2d228ac4117a145f20641d7954482bfc3e 100644 --- a/internal/tui/components/chat/splash/splash.go +++ b/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("."), "", diff --git a/internal/tui/components/dialogs/commands/commands.go b/internal/tui/components/dialogs/commands/commands.go index dfe1cd6ade60da7494eab99ede1f104aa045141a..1e6bfd9fc0791ba45b8c76edc3ca745e0fa53528 100644 --- a/internal/tui/components/dialogs/commands/commands.go +++ b/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, }) }, }, diff --git a/internal/tui/page/chat/chat.go b/internal/tui/page/chat/chat.go index 3d9d550097635380ba9e5f7a9002a8cf69a61d91..f951de8677271dfbf034377afaa492f0d8824889 100644 --- a/internal/tui/page/chat/chat.go +++ b/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