diff --git a/crates/csa-config/src/config.rs b/crates/csa-config/src/config.rs index bb9b6c7..f2452a6 100644 --- a/crates/csa-config/src/config.rs +++ b/crates/csa-config/src/config.rs @@ -169,8 +169,8 @@ fn merge_toml_values(base: toml::Value, overlay: toml::Value) -> toml::Value { impl ProjectConfig { /// Load config with fallback chain: /// - /// 1. If both `.csa/config.toml` (project) and `~/.config/csa/config.toml` (user) exist, - /// deep-merge them with project settings overriding user settings. + /// 1. If both `.csa/config.toml` (project) and `~/.config/cli-sub-agent/config.toml` (user) + /// exist, deep-merge them with project settings overriding user settings. /// 2. If only project config exists, use it directly. /// 3. If only user config exists, use it as fallback. /// 4. If neither exists, return None. @@ -267,12 +267,12 @@ impl ProjectConfig { Ok(Some(config)) } - /// Path to user-level config: `~/.config/csa/config.toml`. + /// Path to user-level config: `~/.config/cli-sub-agent/config.toml`. /// /// Returns None if the config directory cannot be determined /// (e.g., no HOME in containers). pub fn user_config_path() -> Option { - directories::ProjectDirs::from("", "", "csa") + directories::ProjectDirs::from("", "", "cli-sub-agent") .map(|dirs| dirs.config_dir().join("config.toml")) } @@ -374,7 +374,7 @@ impl ProjectConfig { .unwrap_or(false) } - /// Save a user-level config template to `~/.config/csa/config.toml`. + /// Save a user-level config template to `~/.config/cli-sub-agent/config.toml`. /// /// Creates the directory if needed. Returns the path written, or None /// if the config directory cannot be determined. @@ -396,7 +396,7 @@ impl ProjectConfig { /// Generate a commented template for user-level config. pub fn user_config_template() -> String { r#"# CSA User-Level Configuration -# Location: ~/.config/csa/config.toml +# Location: ~/.config/cli-sub-agent/config.toml # # This file provides default tiers, tool settings, and aliases # that apply across all CSA projects unless overridden by diff --git a/crates/csa-config/src/config_merge_tests.rs b/crates/csa-config/src/config_merge_tests.rs index f59e62f..92b572f 100644 --- a/crates/csa-config/src/config_merge_tests.rs +++ b/crates/csa-config/src/config_merge_tests.rs @@ -188,7 +188,7 @@ fn test_user_config_path_returns_some() { if std::env::var("HOME").is_ok() { assert!(path.is_some()); let p = path.unwrap(); - assert!(p.to_string_lossy().contains("csa")); + assert!(p.to_string_lossy().contains("cli-sub-agent")); assert!(p.to_string_lossy().contains("config.toml")); } // In containers without HOME, it's OK to return None @@ -202,4 +202,88 @@ fn test_user_config_template_is_valid() { assert!(template.contains("[resources]")); assert!(template.contains("suppress_notify")); assert!(template.contains("# [tiers.")); + // Template location comment should point to unified path + assert!(template.contains("cli-sub-agent/config.toml")); +} + +#[test] +fn test_user_config_path_matches_global_config_dir() { + // After unification, user-level ProjectConfig and GlobalConfig share the same directory. + if std::env::var("HOME").is_err() { + return; // Skip in containers + } + let user_path = ProjectConfig::user_config_path().unwrap(); + let global_path = crate::GlobalConfig::config_path().unwrap(); + assert_eq!( + user_path.parent(), + global_path.parent(), + "User and global config should share the same directory" + ); +} + +#[test] +fn test_load_user_tiers_without_project_config() { + // When only user-level config exists (no project config), + // load should return user-level tiers. + let tmp = tempfile::tempdir().unwrap(); + let user_path = tmp.path().join("user.toml"); + let project_path = tmp + .path() + .join("nonexistent") + .join(".csa") + .join("config.toml"); + + std::fs::write( + &user_path, + r#" +schema_version = 1 +[tiers.user-tier] +description = "User-level tier" +models = ["codex/openai/o3/medium"] +"#, + ) + .unwrap(); + + let config = ProjectConfig::load_with_paths(Some(&user_path), &project_path) + .unwrap() + .expect("Should load from user path"); + assert!(config.tiers.contains_key("user-tier")); +} + +#[test] +fn test_load_project_overrides_user_tiers() { + // When both user and project configs exist, project tiers + // override user tiers (deep merge). + let tmp = tempfile::tempdir().unwrap(); + let user_path = tmp.path().join("user.toml"); + let project_path = tmp.path().join("project.toml"); + + std::fs::write( + &user_path, + r#" +schema_version = 1 +[tiers.shared-tier] +description = "User version" +models = ["codex/openai/o3/medium"] +"#, + ) + .unwrap(); + + std::fs::write( + &project_path, + r#" +schema_version = 1 +[tiers.shared-tier] +description = "Project version" +models = ["codex/openai/o3/high"] +"#, + ) + .unwrap(); + + let config = ProjectConfig::load_with_paths(Some(&user_path), &project_path) + .unwrap() + .expect("Should load merged config"); + let tier = config.tiers.get("shared-tier").unwrap(); + assert_eq!(tier.description, "Project version"); + assert_eq!(tier.models, vec!["codex/openai/o3/high"]); } diff --git a/crates/csa-config/src/config_tests.rs b/crates/csa-config/src/config_tests.rs index d9d8c15..0dca5b3 100644 --- a/crates/csa-config/src/config_tests.rs +++ b/crates/csa-config/src/config_tests.rs @@ -4,7 +4,7 @@ use tempfile::tempdir; #[test] fn test_load_nonexistent_returns_none() { let dir = tempdir().unwrap(); - // Use load_with_paths to isolate from real ~/.config/csa/config.toml on host. + // Use load_with_paths to isolate from real ~/.config/cli-sub-agent/config.toml on host. let project_path = dir.path().join(".csa").join("config.toml"); let result = ProjectConfig::load_with_paths(None, &project_path).unwrap(); assert!(result.is_none()); diff --git a/skills/install-update-csa/SKILL.md b/skills/install-update-csa/SKILL.md index 05f1eb1..140b251 100644 --- a/skills/install-update-csa/SKILL.md +++ b/skills/install-update-csa/SKILL.md @@ -21,7 +21,7 @@ capability tiers, and sets tool-specific restrictions. - First time installing or setting up CSA - Updating CSA binary to a new version -- Checking or creating global user config (`~/.config/csa/config.toml`) +- Checking or creating global user config (`~/.config/cli-sub-agent/config.toml`) - First time setting up CSA in a project (project-level config) - Reconfiguring tool/model selection for changed requirements - Adding newly installed tools or models @@ -29,7 +29,7 @@ capability tiers, and sets tool-specific restrictions. ## Output - CSA binary verified in PATH -- `~/.config/csa/config.toml` — global user configuration (TOML) +- `~/.config/cli-sub-agent/config.toml` — global user configuration (TOML) - `.csa/config.toml` — project configuration (TOML) - `.csa/` added to `.gitignore` @@ -63,10 +63,10 @@ Report the version. If it looks outdated, suggest updating. ### Phase 0.5: Check Global Config -Check if the global user config exists at `~/.config/csa/config.toml`: +Check if the global user config exists at `~/.config/cli-sub-agent/config.toml`: ```bash -ls ~/.config/csa/config.toml 2>/dev/null +ls ~/.config/cli-sub-agent/config.toml 2>/dev/null ``` - If **not found**: @@ -81,7 +81,7 @@ ls ~/.config/csa/config.toml 2>/dev/null **Key settings to include in global config**: ```toml -# ~/.config/csa/config.toml +# ~/.config/cli-sub-agent/config.toml # Global defaults — project configs override these values. [tools.codex]