Skip to main content

agentstatedeveloper_cli/
config.rs

1//! Minimal solo-developer config. Resolves the ASD database path from
2//! (in priority order): CLI flag, `ASD_DB` env var, then a default:
3//! `./.asd-state.db` if present, else (Plan Q t-002) a git-style walk-up for
4//! the nearest `.asd-state.db`, else the registry's active repo (`asd repo
5//! use`), else `./.asd-state.db`.
6//! Policy file from `--policy <path>` or `ASD_POLICY` env var (optional).
7
8use std::path::PathBuf;
9
10/// Default agent id recorded on ASG commits produced by the CLI.
11pub const DEFAULT_AGENT_ID: &str = "asd-cli-user";
12
13#[derive(Debug, Clone)]
14pub struct Config {
15    pub db_path: PathBuf,
16    pub agent_id: String,
17    pub policy_path: Option<PathBuf>,
18    pub audit_log_path: Option<PathBuf>,
19    /// Plan D t-001: brief output mode. Set via `--brief` flag or
20    /// `ASD_FORMAT=brief` env var. When true, commands project their
21    /// JSON output down to load-bearing fields for ~60-80% token cut.
22    pub brief: bool,
23}
24
25impl Config {
26    /// Resolve config from optional explicit paths.
27    pub fn resolve(
28        explicit_db: Option<PathBuf>,
29        explicit_policy: Option<PathBuf>,
30        explicit_audit_log: Option<PathBuf>,
31    ) -> Self {
32        // Generic helper — conservative (local-only) default.
33        Self::resolve_with_brief(
34            explicit_db,
35            explicit_policy,
36            explicit_audit_log,
37            false,
38            true,
39        )
40    }
41
42    /// `local_only = true` restricts the default to `./.asd-state.db` (for
43    /// commands that CREATE or populate a db in place — `init`/`onboard`/`index`
44    /// — which must never target a parent project or the registry's active
45    /// repo). `false` enables the Plan Q t-002 walk-up + registry-active
46    /// fallback used by read/query commands.
47    pub fn resolve_with_brief(
48        explicit_db: Option<PathBuf>,
49        explicit_policy: Option<PathBuf>,
50        explicit_audit_log: Option<PathBuf>,
51        brief_flag: bool,
52        local_only: bool,
53    ) -> Self {
54        let db_path = explicit_db
55            .or_else(|| std::env::var_os("ASD_DB").map(PathBuf::from))
56            .unwrap_or_else(|| {
57                if local_only {
58                    PathBuf::from("./.asd-state.db")
59                } else {
60                    resolve_default_db()
61                }
62            });
63        let policy_path =
64            explicit_policy.or_else(|| std::env::var_os("ASD_POLICY").map(PathBuf::from));
65        let audit_log_path =
66            explicit_audit_log.or_else(|| std::env::var_os("ASD_AUDIT_LOG").map(PathBuf::from));
67        let brief = brief_flag
68            || std::env::var("ASD_FORMAT")
69                .map(|v| v.eq_ignore_ascii_case("brief"))
70                .unwrap_or(false);
71        Self {
72            db_path,
73            agent_id: DEFAULT_AGENT_ID.to_string(),
74            policy_path,
75            audit_log_path,
76            brief,
77        }
78    }
79}
80
81/// Default db path when neither `--db` nor `ASD_DB` is given (Plan Q t-002).
82/// Backward-compatible: a `.asd-state.db` in the current directory always wins,
83/// so existing single-repo usage is unchanged. Only when there is none do we
84/// walk up for the nearest one (so `asd` works from any subdirectory), then
85/// fall back to the registry's active repo, then the plain cwd path.
86fn resolve_default_db() -> PathBuf {
87    let cwd_db = PathBuf::from("./.asd-state.db");
88    if cwd_db.exists() {
89        return cwd_db;
90    }
91    // Shared with `asd-mcp` so both surfaces resolve identically (Plan S t-004).
92    if let Some(found) = agentstatedeveloper_core::registry::find_db_upwards() {
93        return found;
94    }
95    if let Some(active) = registry_active_db() {
96        return active;
97    }
98    cwd_db
99}
100
101/// The registry's active repo db path (`asd repo use <name>`), if any.
102fn registry_active_db() -> Option<PathBuf> {
103    let reg = agentstatedeveloper_core::registry::Registry::load().ok()?;
104    reg.active().map(|e| e.path.clone())
105}