claude_agent/context/
level.rs

1//! Memory provider for aggregating content from multiple resource levels.
2//!
3//! Provides leveled resource loading with fixed override order:
4//! Enterprise → User → Project → Local (later levels override earlier).
5
6use std::path::PathBuf;
7
8use async_trait::async_trait;
9
10use super::{ContextResult, MemoryContent, MemoryProvider, RuleIndex};
11
12/// Memory provider that aggregates content from multiple resource levels.
13///
14/// Contents are merged in the order they are added, with later additions
15/// taking precedence. When used with the CLI resource loading methods,
16/// the order is always: Enterprise → User → Project → Local.
17#[derive(Debug, Default)]
18pub struct LeveledMemoryProvider {
19    contents: Vec<MemoryContent>,
20}
21
22impl LeveledMemoryProvider {
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Adds raw CLAUDE.md content.
28    pub fn add_content(&mut self, content: impl Into<String>) {
29        let mut mc = MemoryContent::default();
30        mc.claude_md.push(content.into());
31        self.contents.push(mc);
32    }
33
34    /// Adds raw CLAUDE.local.md content.
35    pub fn add_local_content(&mut self, content: impl Into<String>) {
36        let mut mc = MemoryContent::default();
37        mc.local_md.push(content.into());
38        self.contents.push(mc);
39    }
40
41    /// Adds a rule index for progressive disclosure.
42    pub fn add_rule(&mut self, rule: RuleIndex) {
43        let mut mc = MemoryContent::default();
44        mc.rule_indices.push(rule);
45        self.contents.push(mc);
46    }
47
48    /// Adds pre-loaded memory content from a MemoryLoader.
49    pub fn add_memory_content(&mut self, content: MemoryContent) {
50        self.contents.push(content);
51    }
52}
53
54#[async_trait]
55impl MemoryProvider for LeveledMemoryProvider {
56    fn name(&self) -> &str {
57        "leveled"
58    }
59
60    async fn load(&self) -> ContextResult<MemoryContent> {
61        let mut combined = MemoryContent::default();
62        for content in &self.contents {
63            combined.merge(content.clone());
64        }
65        Ok(combined)
66    }
67
68    fn priority(&self) -> i32 {
69        100
70    }
71}
72
73pub fn enterprise_base_path() -> Option<PathBuf> {
74    #[cfg(target_os = "macos")]
75    {
76        let path = PathBuf::from("/Library/Application Support/ClaudeCode");
77        if path.exists() {
78            return Some(path);
79        }
80    }
81    #[cfg(target_os = "linux")]
82    {
83        let path = PathBuf::from("/etc/claude-code");
84        if path.exists() {
85            return Some(path);
86        }
87    }
88    None
89}
90
91pub fn user_base_path() -> Option<PathBuf> {
92    crate::common::home_dir().map(|h| h.join(".claude"))
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[tokio::test]
100    async fn test_leveled_memory_provider() {
101        let mut provider = LeveledMemoryProvider::new();
102        provider.add_content("# Enterprise Rules");
103        provider.add_content("# User Preferences");
104        provider.add_content("# Project Guidelines");
105
106        let content = provider.load().await.unwrap();
107        assert_eq!(content.claude_md.len(), 3);
108    }
109
110    #[tokio::test]
111    async fn test_leveled_with_local() {
112        let mut provider = LeveledMemoryProvider::new();
113        provider.add_content("Main content");
114        provider.add_local_content("Local content");
115
116        let content = provider.load().await.unwrap();
117        assert_eq!(content.claude_md.len(), 1);
118        assert_eq!(content.local_md.len(), 1);
119
120        let combined = content.combined_claude_md();
121        assert!(combined.contains("Main content"));
122        assert!(combined.contains("Local content"));
123    }
124}