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}