use serde::{Deserialize, Serialize};
use std::path::PathBuf;
mod id_strategy;
mod runtime;
mod template;
pub use id_strategy::IdStrategy;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
#[serde(skip)]
pub gov_root: PathBuf,
#[serde(default)]
pub project: ProjectConfig,
#[serde(default)]
pub paths: PathsConfig,
#[serde(default)]
pub schema: SchemaConfig,
#[serde(default)]
pub source_scan: SourceScanConfig,
#[serde(default)]
pub work_item: WorkItemConfig,
#[serde(default)]
pub verification: VerificationConfig,
#[serde(default)]
pub concurrency: ConcurrencyConfig,
#[serde(default)]
pub tags: TagsConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
gov_root: PathBuf::from("gov"),
project: ProjectConfig::default(),
paths: PathsConfig::default(),
schema: SchemaConfig::default(),
source_scan: SourceScanConfig::default(),
work_item: WorkItemConfig::default(),
verification: VerificationConfig::default(),
concurrency: ConcurrencyConfig::default(),
tags: TagsConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct TagsConfig {
#[serde(default)]
pub allowed: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VerificationConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default)]
pub default_guards: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConcurrencyConfig {
#[serde(default = "default_lock_timeout_secs")]
pub lock_timeout_secs: u64,
}
fn default_lock_timeout_secs() -> u64 {
30
}
impl Default for ConcurrencyConfig {
fn default() -> Self {
Self {
lock_timeout_secs: default_lock_timeout_secs(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ProjectConfig {
#[serde(default = "default_project_name")]
pub name: String,
#[serde(default = "default_owner")]
pub default_owner: String,
}
fn default_project_name() -> String {
"govctl-project".to_string()
}
fn default_owner() -> String {
if let Ok(owner) = std::env::var("GOVCTL_DEFAULT_OWNER") {
return owner;
}
git_config_value("user.name")
.map(|name| format!("@{name}"))
.filter(|owner| owner.len() > 1)
.unwrap_or_else(|| "@your-handle".to_string())
}
fn git_config_value(key: &str) -> Option<String> {
let output = std::process::Command::new("git")
.args(["config", key])
.output()
.ok()?;
if !output.status.success() {
return None;
}
let value = String::from_utf8(output.stdout).ok()?;
let value = value.trim();
if value.is_empty() {
return None;
}
Some(value.to_string())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PathsConfig {
#[serde(default = "default_docs_output")]
pub docs_output: PathBuf,
#[serde(default = "default_agent_dir")]
pub agent_dir: PathBuf,
}
fn default_docs_output() -> PathBuf {
PathBuf::from("docs")
}
pub fn default_agent_dir() -> PathBuf {
PathBuf::from(".claude")
}
impl Default for PathsConfig {
fn default() -> Self {
Self {
docs_output: default_docs_output(),
agent_dir: default_agent_dir(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaConfig {
#[serde(default = "default_schema_version")]
pub version: u32,
}
fn default_schema_version() -> u32 {
crate::cmd::migrate::CURRENT_SCHEMA_VERSION
}
impl Default for SchemaConfig {
fn default() -> Self {
Self {
version: default_schema_version(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SourceScanConfig {
#[serde(default)]
pub enabled: bool,
#[serde(default = "default_scan_include")]
pub include: Vec<String>,
#[serde(default)]
pub exclude: Vec<String>,
#[serde(default = "default_scan_pattern")]
pub pattern: String,
}
fn default_scan_include() -> Vec<String> {
vec![
"src/**/*.rs".to_string(),
"crates/**/*.rs".to_string(),
"**/*.md".to_string(),
]
}
fn default_scan_pattern() -> String {
r"\[\[(RFC-\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\d{4}|WI-\d{4}-\d{2}-\d{2}-(?:[a-f0-9]{4}(?:-\d{3})?|\d{3}))\]\]".to_string()
}
impl Default for SourceScanConfig {
fn default() -> Self {
Self {
enabled: false,
include: default_scan_include(),
exclude: vec![],
pattern: default_scan_pattern(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkItemConfig {
#[serde(default)]
pub id_strategy: IdStrategy,
}
impl Default for WorkItemConfig {
fn default() -> Self {
Self {
id_strategy: IdStrategy::Sequential,
}
}
}