use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ThinkMode {
Think,
ThinkHard,
ThinkHarder,
UltraThink,
MegaThink,
}
impl ThinkMode {
pub fn to_prompt_suffix(&self) -> &str {
match self {
ThinkMode::Think => "think",
ThinkMode::ThinkHard => "think hard",
ThinkMode::ThinkHarder => "think harder",
ThinkMode::UltraThink => "ultrathink",
ThinkMode::MegaThink => "megathink",
}
}
}
impl std::fmt::Display for ThinkMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_prompt_suffix())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
pub enum OutputFormat {
#[default]
Text,
Json,
StreamJson,
}
impl OutputFormat {
pub fn as_cli_arg(&self) -> &'static str {
match self {
OutputFormat::Text => "text",
OutputFormat::Json => "json",
OutputFormat::StreamJson => "stream-json",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClaudeConfig {
pub model: String,
#[serde(rename = "dangerously_skip_permissions")]
pub dangerous_skip: bool,
pub think_mode: Option<ThinkMode>,
pub json_output: bool,
#[serde(default)]
pub output_format: OutputFormat,
#[serde(default)]
pub append_system_prompt: Option<String>,
#[serde(default)]
pub max_turns: Option<u32>,
pub custom_commands: Vec<String>,
#[serde(rename = "mcpServers", default)]
pub mcp_servers: HashMap<String, serde_json::Value>,
#[serde(default)]
pub use_real_api: bool,
}
impl Default for ClaudeConfig {
fn default() -> Self {
Self {
model: "claude-3.5-sonnet".to_string(),
dangerous_skip: true,
think_mode: Some(ThinkMode::Think),
json_output: true,
output_format: OutputFormat::Json,
append_system_prompt: None,
max_turns: None,
custom_commands: Vec::new(),
mcp_servers: HashMap::new(),
use_real_api: false,
}
}
}
impl ClaudeConfig {
pub fn for_master() -> Self {
Self {
model: "claude-3.5-sonnet".to_string(),
dangerous_skip: true,
think_mode: Some(ThinkMode::UltraThink),
json_output: true,
output_format: OutputFormat::Json,
append_system_prompt: None,
max_turns: None,
custom_commands: vec![
"ccswarm status".to_string(),
"ccswarm review".to_string(),
"ccswarm deploy".to_string(),
"ccswarm quality-gate".to_string(),
],
mcp_servers: HashMap::new(),
use_real_api: false,
}
}
pub fn for_agent(role: &str) -> Self {
let think_mode = match role {
"frontend" | "backend" => Some(ThinkMode::ThinkHard),
"devops" => Some(ThinkMode::Think),
"qa" => Some(ThinkMode::ThinkHard),
_ => Some(ThinkMode::Think),
};
let custom_commands = match role {
"frontend" => vec![
"npm test".to_string(),
"npm run lint".to_string(),
"npm run build".to_string(),
],
"backend" => vec![
"npm test".to_string(),
"npm run migrate".to_string(),
"npm run api-test".to_string(),
],
"devops" => vec![
"terraform plan".to_string(),
"kubectl get pods".to_string(),
"docker build".to_string(),
],
"qa" => vec![
"npm test".to_string(),
"npm run e2e".to_string(),
"npm run coverage".to_string(),
],
_ => Vec::new(),
};
Self {
model: "claude-3.5-sonnet".to_string(),
dangerous_skip: true,
think_mode,
json_output: true,
output_format: OutputFormat::Json,
append_system_prompt: None,
max_turns: None,
custom_commands,
mcp_servers: HashMap::new(),
use_real_api: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub specialization: String,
pub worktree: String,
pub branch: String,
pub claude_config: ClaudeConfig,
pub claude_md_template: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectConfig {
pub name: String,
pub repository: RepositoryConfig,
pub master_claude: MasterClaudeConfig,
}
impl Default for ProjectConfig {
fn default() -> Self {
Self {
name: "default-project".to_string(),
repository: RepositoryConfig::default(),
master_claude: MasterClaudeConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RepositoryConfig {
pub url: String,
pub main_branch: String,
}
impl Default for RepositoryConfig {
fn default() -> Self {
Self {
url: "https://github.com/example/repo.git".to_string(),
main_branch: "main".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MasterClaudeConfig {
pub role: String,
pub quality_threshold: f64,
pub think_mode: ThinkMode,
pub permission_level: String,
pub claude_config: ClaudeConfig,
#[serde(default = "default_proactive_mode")]
pub enable_proactive_mode: bool,
#[serde(default = "default_proactive_frequency")]
pub proactive_frequency: u64,
#[serde(default = "default_high_frequency")]
pub high_frequency: u64,
}
fn default_proactive_mode() -> bool {
true }
fn default_proactive_frequency() -> u64 {
30 }
fn default_high_frequency() -> u64 {
15 }
impl Default for MasterClaudeConfig {
fn default() -> Self {
Self {
role: "master".to_string(),
quality_threshold: 0.85,
think_mode: ThinkMode::Think,
permission_level: "standard".to_string(),
claude_config: ClaudeConfig::default(),
enable_proactive_mode: default_proactive_mode(),
proactive_frequency: default_proactive_frequency(),
high_frequency: default_high_frequency(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoordinationConfig {
pub communication_method: String,
pub sync_interval: u64,
pub quality_gate_frequency: String,
pub master_review_trigger: String,
}
impl Default for CoordinationConfig {
fn default() -> Self {
Self {
communication_method: "json".to_string(),
sync_interval: 30,
quality_gate_frequency: "on_task_completion".to_string(),
master_review_trigger: "auto".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CcswarmConfig {
pub project: ProjectConfig,
#[serde(default)]
pub agents: HashMap<String, AgentConfig>,
pub coordination: CoordinationConfig,
}
impl CcswarmConfig {
pub async fn from_file(path: PathBuf) -> anyhow::Result<Self> {
let contents = tokio::fs::read_to_string(path).await?;
let config: Self = serde_json::from_str(&contents)?;
Ok(config)
}
pub async fn to_file(&self, path: PathBuf) -> anyhow::Result<()> {
let contents = serde_json::to_string_pretty(self)?;
tokio::fs::write(path, contents).await?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_config_creation() {
let config = ClaudeConfig::default();
assert_eq!(config.model, "claude-3.5-sonnet");
assert!(config.dangerous_skip);
}
}