diff --git a/internal/config/config.go b/internal/config/config.go index 6b179abebbffe425ab624217ce40cb55ccbf7877..db6125017282f6e401c76002894c191e23e14d7c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -259,13 +259,17 @@ 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"` - 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"` - 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"` - DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"` - DataDirectory string `json:"data_directory,omitempty" jsonschema:"description=Directory for storing application data (relative to working directory),default=.crush,example=.crush"` // Relative to the cwd + ContextPaths []string `json:"context_paths,omitempty" jsonschema:"description=Paths to files containing context information for the AI,example=.cursorrules,example=CRUSH.md"` + 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"` + 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"` + DisableAutoSummarize bool `json:"disable_auto_summarize,omitempty" jsonschema:"description=Disable automatic conversation summarization,default=false"` + // DataDirectory is where Crush keeps per-project state such as + // the SQLite database and workspace overrides. Relative paths are + // resolved against the working directory; absolute paths are used + // verbatim. After defaulting the stored value is always absolute. + DataDirectory string `json:"data_directory,omitempty" jsonschema:"description=Directory for storing application data. Relative paths are resolved against the working directory; absolute paths are used as-is.,default=.crush,example=.crush"` DisabledTools []string `json:"disabled_tools,omitempty" jsonschema:"description=List of built-in tools to disable and hide from the agent,example=bash,example=sourcegraph"` DisableProviderAutoUpdate bool `json:"disable_provider_auto_update,omitempty" jsonschema:"description=Disable providers auto-update,default=false"` DisableDefaultProviders bool `json:"disable_default_providers,omitempty" jsonschema:"description=Ignore all default/embedded providers. When enabled\\, providers must be fully specified in the config file with base_url\\, models\\, and api_key - no merging with defaults occurs,default=false"` diff --git a/internal/config/load_test.go b/internal/config/load_test.go index f65273fc14fc5cb000f85a6c2e4f1a22a7fd1d01..cb69976f7f8130e6023fde36629790a86d5138f1 100644 --- a/internal/config/load_test.go +++ b/internal/config/load_test.go @@ -205,6 +205,36 @@ func TestConfig_setDefaults(t *testing.T) { require.Equal(t, filepath.Join(workingDir, "state"), cfg.Options.DataDirectory) }) + t.Run("preserves absolute configured data directory", func(t *testing.T) { + // Use a platform-appropriate absolute path so the test runs + // the same way on POSIX and Windows. + absDir := filepath.Join(t.TempDir(), "data") + cfg := &Config{Options: &Options{DataDirectory: absDir}} + + cfg.setDefaults(filepath.Join(t.TempDir(), "worktree"), "") + + require.Equal(t, absDir, cfg.Options.DataDirectory) + }) + + t.Run("workspace merge re-entry keeps an absolute data directory", func(t *testing.T) { + // Simulate the load and reload paths: defaults are applied + // twice with the data directory potentially carried through + // from an earlier merge as a relative string. + workingDir := filepath.Join(t.TempDir(), "worktree") + cfg := &Config{} + cfg.setDefaults(workingDir, "") + + // Workspace JSON sets data_directory to a relative value; the + // merge replaces the struct, then setDefaults runs again. + cfg.Options.DataDirectory = "./state" + cfg.setDefaults(workingDir, "") + + require.True(t, filepath.IsAbs(cfg.Options.DataDirectory), + "data directory must remain absolute after re-merge, got %q", + cfg.Options.DataDirectory) + require.Equal(t, filepath.Join(workingDir, "state"), cfg.Options.DataDirectory) + }) + t.Run("does not adopt .crush from a parent project", func(t *testing.T) { parent := t.TempDir() diff --git a/internal/swagger/docs.go b/internal/swagger/docs.go index e36cae3f015d0cfc474bffd9f697e6170cc5a73a..ec106954f54e055d1ebe9ff64db9c63454a644e2 100644 --- a/internal/swagger/docs.go +++ b/internal/swagger/docs.go @@ -2939,7 +2939,7 @@ const docTemplate = `{ } }, "data_directory": { - "description": "Relative to the cwd", + "description": "DataDirectory is where Crush keeps per-project state such as the SQLite database and workspace overrides. Relative paths are resolved against the working directory; absolute paths are used verbatim. After defaulting the stored value is always absolute.", "type": "string" }, "debug": { diff --git a/internal/swagger/swagger.json b/internal/swagger/swagger.json index 3785d226e8d08af1be38786f83cea1f400c3eb68..b3ccbe22b783b78f508fa1a6b05f38d2f45f8612 100644 --- a/internal/swagger/swagger.json +++ b/internal/swagger/swagger.json @@ -2932,7 +2932,7 @@ } }, "data_directory": { - "description": "Relative to the cwd", + "description": "DataDirectory is where Crush keeps per-project state such as the SQLite database and workspace overrides. Relative paths are resolved against the working directory; absolute paths are used verbatim. After defaulting the stored value is always absolute.", "type": "string" }, "debug": { diff --git a/internal/swagger/swagger.yaml b/internal/swagger/swagger.yaml index f0de0979b842425fa5b5287377aa1d1f18b9a4e5..d1265256c785236bac581401c7c6e44838ff5dae 100644 --- a/internal/swagger/swagger.yaml +++ b/internal/swagger/swagger.yaml @@ -287,7 +287,11 @@ definitions: type: string type: array data_directory: - description: Relative to the cwd + description: |- + DataDirectory is where Crush keeps per-project state such as the SQLite + database and workspace overrides. Relative paths are resolved against + the working directory; absolute paths are used verbatim. After + defaulting the stored value is always absolute. type: string debug: type: boolean diff --git a/schema.json b/schema.json index 8b98269414175422347ae208f34997081cdc6249..751a5f529f8cb2773286d1dbd98b99b548c6503b 100644 --- a/schema.json +++ b/schema.json @@ -423,7 +423,7 @@ }, "data_directory": { "type": "string", - "description": "Directory for storing application data (relative to working directory)", + "description": "Directory for storing application data. Relative paths are resolved against the working directory; absolute paths are used as-is.", "default": ".crush", "examples": [ ".crush"