1use std::path::Path;
2
3use crate::config::ContextConfig;
4
5pub struct AgentsContext {
6 content: Option<String>,
7}
8
9impl AgentsContext {
10 pub fn load(cwd: &str, cfg: &ContextConfig) -> Self {
11 let mut parts: Vec<String> = Vec::new();
12
13 if cfg.auto_load_global {
14 let config_dir = crate::config::Config::config_dir();
15 for name in &["AGENTS.md", "AGENT.md"] {
16 let path = config_dir.join(name);
17 if path.exists() {
18 match std::fs::read_to_string(&path) {
19 Ok(content) => {
20 let trimmed = content.trim().to_string();
21 if !trimmed.is_empty() {
22 tracing::debug!(
23 "Loaded global agents context from {}",
24 path.display()
25 );
26 parts.push(trimmed);
27 }
28 }
29 Err(e) => {
30 tracing::warn!("Failed to read {}: {}", path.display(), e);
31 }
32 }
33 break;
34 }
35 }
36 }
37
38 if cfg.auto_load_project {
39 let cwd_path = Path::new(cwd);
40 let candidates = [
41 cwd_path.join("AGENTS.md"),
42 cwd_path.join("AGENT.md"),
43 cwd_path.join(".dot").join("AGENTS.md"),
44 cwd_path.join(".dot").join("AGENT.md"),
45 ];
46 for path in &candidates {
47 if path.exists() {
48 match std::fs::read_to_string(path) {
49 Ok(content) => {
50 let trimmed = content.trim().to_string();
51 if !trimmed.is_empty() {
52 tracing::debug!(
53 "Loaded project agents context from {}",
54 path.display()
55 );
56 parts.push(trimmed);
57 }
58 }
59 Err(e) => {
60 tracing::warn!("Failed to read {}: {}", path.display(), e);
61 }
62 }
63 break;
64 }
65 }
66 }
67
68 let content = if parts.is_empty() {
69 None
70 } else {
71 Some(parts.join("\n\n---\n\n"))
72 };
73
74 AgentsContext { content }
75 }
76
77 pub fn apply_to_system_prompt(&self, base: &str) -> String {
78 match &self.content {
79 None => base.to_string(),
80 Some(ctx) => format!("{ctx}\n\n---\n\n{base}"),
81 }
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.content.is_none()
86 }
87}