use serde::Deserialize;
use std::collections::HashMap;
#[derive(Debug, Clone, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Config {
#[serde(default = "default_branch_tracking")]
pub branch_tracking: BranchTracking,
#[serde(default = "default_wip_limit")]
pub wip_limit: usize,
#[serde(default = "default_worktree_policy")]
pub worktrees: WorktreePolicy,
#[serde(default)]
pub checklist_templates: HashMap<String, ChecklistTemplate>,
#[serde(default)]
pub default_checklist_type: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BranchTracking {
Required,
Optional,
Disabled,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum WorktreePolicy {
Required,
Optional,
Disabled,
}
fn default_branch_tracking() -> BranchTracking {
BranchTracking::Optional
}
fn default_worktree_policy() -> WorktreePolicy {
WorktreePolicy::Optional
}
fn default_wip_limit() -> usize {
0
}
#[derive(Debug, Clone, Deserialize)]
pub struct ChecklistTemplate {
pub items: Vec<String>,
}
impl Config {
pub fn load() -> Self {
let path = match crate::registry::resolve_or_create_identity() {
Ok(ctx) => crate::registry::global_config_path(&ctx.id),
Err(_) => return Config::defaults(),
};
if path.exists() {
match std::fs::read_to_string(&path) {
Ok(contents) => match toml::from_str(&contents) {
Ok(config) => return config,
Err(e) => {
eprintln!("warning: {} parse error: {e}", path.display());
eprintln!(" using defaults");
}
},
Err(e) => {
eprintln!("warning: could not read {}: {e}", path.display());
}
}
}
Config::defaults()
}
pub fn defaults() -> Self {
Self {
branch_tracking: BranchTracking::Optional,
wip_limit: 0,
worktrees: WorktreePolicy::Optional,
checklist_templates: HashMap::new(),
default_checklist_type: None,
}
}
pub fn get_checklist_items(&self, typ: &str) -> Option<Vec<String>> {
if let Some(tmpl) = self.checklist_templates.get(typ) {
return Some(tmpl.items.clone());
}
builtin_template(typ).map(|items| items.iter().map(|s| s.to_string()).collect())
}
}
fn builtin_template(typ: &str) -> Option<&'static [&'static str]> {
match typ {
"feat" => Some(&[
"Implementation plan drafted",
"Tests written for new functionality",
"Edge cases handled",
"Documentation updated",
]),
"fix" => Some(&[
"Root cause confirmed",
"Regression test added",
"All callers handled",
"Fix verified against edge cases",
]),
"research" => Some(&[
"Sources found and reviewed",
"Claims verified",
"Tradeoffs documented",
"Recommendation stated",
]),
"chore" => Some(&[
"No side effects confirmed",
"Cleanup scope defined",
"Dependencies updated (if any)",
"Automation verified",
]),
"default" => None, _ => None,
}
}