agentstatedeveloper-cli 1.4.9

asd CLI — command-line interface for AgentStateDeveloper
Documentation
//! Minimal solo-developer config. Resolves the ASD database path from
//! (in priority order): CLI flag, `ASD_DB` env var, then a default:
//! `./.asd-state.db` if present, else (Plan Q t-002) a git-style walk-up for
//! the nearest `.asd-state.db`, else the registry's active repo (`asd repo
//! use`), else `./.asd-state.db`.
//! Policy file from `--policy <path>` or `ASD_POLICY` env var (optional).

use std::path::PathBuf;

/// Default agent id recorded on ASG commits produced by the CLI.
pub const DEFAULT_AGENT_ID: &str = "asd-cli-user";

#[derive(Debug, Clone)]
pub struct Config {
    pub db_path: PathBuf,
    pub agent_id: String,
    pub policy_path: Option<PathBuf>,
    pub audit_log_path: Option<PathBuf>,
    /// Plan D t-001: brief output mode. Set via `--brief` flag or
    /// `ASD_FORMAT=brief` env var. When true, commands project their
    /// JSON output down to load-bearing fields for ~60-80% token cut.
    pub brief: bool,
}

impl Config {
    /// Resolve config from optional explicit paths.
    pub fn resolve(
        explicit_db: Option<PathBuf>,
        explicit_policy: Option<PathBuf>,
        explicit_audit_log: Option<PathBuf>,
    ) -> Self {
        // Generic helper — conservative (local-only) default.
        Self::resolve_with_brief(
            explicit_db,
            explicit_policy,
            explicit_audit_log,
            false,
            true,
        )
    }

    /// `local_only = true` restricts the default to `./.asd-state.db` (for
    /// commands that CREATE or populate a db in place — `init`/`onboard`/`index`
    /// — which must never target a parent project or the registry's active
    /// repo). `false` enables the Plan Q t-002 walk-up + registry-active
    /// fallback used by read/query commands.
    pub fn resolve_with_brief(
        explicit_db: Option<PathBuf>,
        explicit_policy: Option<PathBuf>,
        explicit_audit_log: Option<PathBuf>,
        brief_flag: bool,
        local_only: bool,
    ) -> Self {
        let db_path = explicit_db
            .or_else(|| std::env::var_os("ASD_DB").map(PathBuf::from))
            .unwrap_or_else(|| {
                if local_only {
                    PathBuf::from("./.asd-state.db")
                } else {
                    resolve_default_db()
                }
            });
        let policy_path =
            explicit_policy.or_else(|| std::env::var_os("ASD_POLICY").map(PathBuf::from));
        let audit_log_path =
            explicit_audit_log.or_else(|| std::env::var_os("ASD_AUDIT_LOG").map(PathBuf::from));
        let brief = brief_flag
            || std::env::var("ASD_FORMAT")
                .map(|v| v.eq_ignore_ascii_case("brief"))
                .unwrap_or(false);
        Self {
            db_path,
            agent_id: DEFAULT_AGENT_ID.to_string(),
            policy_path,
            audit_log_path,
            brief,
        }
    }
}

/// Default db path when neither `--db` nor `ASD_DB` is given (Plan Q t-002).
/// Backward-compatible: a `.asd-state.db` in the current directory always wins,
/// so existing single-repo usage is unchanged. Only when there is none do we
/// walk up for the nearest one (so `asd` works from any subdirectory), then
/// fall back to the registry's active repo, then the plain cwd path.
fn resolve_default_db() -> PathBuf {
    let cwd_db = PathBuf::from("./.asd-state.db");
    if cwd_db.exists() {
        return cwd_db;
    }
    // Shared with `asd-mcp` so both surfaces resolve identically (Plan S t-004).
    if let Some(found) = agentstatedeveloper_core::registry::find_db_upwards() {
        return found;
    }
    if let Some(active) = registry_active_db() {
        return active;
    }
    cwd_db
}

/// The registry's active repo db path (`asd repo use <name>`), if any.
fn registry_active_db() -> Option<PathBuf> {
    let reg = agentstatedeveloper_core::registry::Registry::load().ok()?;
    reg.active().map(|e| e.path.clone())
}