From 982e65aec5df9728080ca0cf763ee9b6619de54e Mon Sep 17 00:00:00 2001 From: dave Date: Fri, 17 Apr 2026 12:39:18 +0000 Subject: [PATCH] huskies: merge 594_story_scaffold_project_toml_includes_all_configurable_settings_with_comments --- server/src/io/fs/scaffold.rs | 90 ++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/server/src/io/fs/scaffold.rs b/server/src/io/fs/scaffold.rs index e8063367..d68b3b72 100644 --- a/server/src/io/fs/scaffold.rs +++ b/server/src/io/fs/scaffold.rs @@ -100,6 +100,24 @@ const DEFAULT_PROJECT_SETTINGS_TOML: &str = r#"# Project-wide default QA mode: " # Per-story `qa` front matter overrides this setting. default_qa = "server" +# Maximum number of retries per story per pipeline stage before marking as blocked. +# Set to 0 to disable retry limits. +max_retries = 2 + +# Default model for coder-stage agents (e.g. "sonnet", "opus"). +# When set, only coder agents whose model matches this value are considered for +# auto-assignment, so opus agents are only used when explicitly requested via +# story front matter `agent:` field. +# default_coder_model = "sonnet" + +# Maximum number of concurrent coder-stage agents. +# Stories wait in 2_current/ until a slot frees up. +# max_coders = 3 + +# Override the base branch for worktree creation and merge operations. +# When not set, the system auto-detects the base branch from the current HEAD. +# base_branch = "main" + # Suppress soft rate-limit warning notifications in chat. # Hard blocks and story-blocked notifications are always sent. # rate_limit_notifications = true @@ -759,6 +777,78 @@ mod tests { ); } + #[test] + fn scaffold_project_toml_contains_max_retries_with_default_value() { + let dir = tempdir().unwrap(); + scaffold_story_kit(dir.path(), 3001).unwrap(); + + let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap(); + assert!( + content.contains("max_retries = 2"), + "project.toml scaffold should include max_retries with default value 2" + ); + assert!( + content.contains("Maximum number of retries"), + "project.toml scaffold should include a comment explaining max_retries" + ); + } + + #[test] + fn scaffold_project_toml_contains_commented_out_optional_fields() { + let dir = tempdir().unwrap(); + scaffold_story_kit(dir.path(), 3001).unwrap(); + + let content = fs::read_to_string(dir.path().join(".huskies/project.toml")).unwrap(); + assert!( + content.contains("# default_coder_model"), + "project.toml scaffold should include commented-out default_coder_model" + ); + assert!( + content.contains("# max_coders"), + "project.toml scaffold should include commented-out max_coders" + ); + assert!( + content.contains("# base_branch"), + "project.toml scaffold should include commented-out base_branch" + ); + } + + #[test] + fn scaffold_project_toml_round_trips_through_project_config_load() { + use crate::config::ProjectConfig; + + let dir = tempdir().unwrap(); + scaffold_story_kit(dir.path(), 3001).unwrap(); + + // The generated project.toml must parse without error. + let config = ProjectConfig::load(dir.path()) + .expect("Generated project.toml should parse without error"); + + // Key defaults must survive the round-trip. + assert_eq!(config.default_qa, "server"); + assert_eq!(config.max_retries, 2); + assert!( + config.rate_limit_notifications, + "rate_limit_notifications should default to true" + ); + assert!( + config.default_coder_model.is_none(), + "default_coder_model should be None when commented out" + ); + assert!( + config.max_coders.is_none(), + "max_coders should be None when commented out" + ); + assert!( + config.base_branch.is_none(), + "base_branch should be None when commented out" + ); + assert!( + config.timezone.is_none(), + "timezone should be None when commented out" + ); + } + #[test] fn scaffold_context_is_blank_template_not_story_kit_content() { let dir = tempdir().unwrap();