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
69pub fn enterprise_base_path() -> Option<PathBuf> {
70    #[cfg(target_os = "macos")]
71    {
72        let path = PathBuf::from("/Library/Application Support/ClaudeCode");
73        if path.exists() {
74            return Some(path);
75        }
76    }
77    #[cfg(target_os = "linux")]
78    {
79        let path = PathBuf::from("/etc/claude-code");
80        if path.exists() {
81            return Some(path);
82        }
83    }
84    None
85}
86
87pub fn user_base_path() -> Option<PathBuf> {
88    crate::common::home_dir().map(|h| h.join(".claude"))
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[tokio::test]
96    async fn test_leveled_memory_provider() {
97        let mut provider = LeveledMemoryProvider::new();
98        provider.add_content("# Enterprise Rules");
99        provider.add_content("# User Preferences");
100        provider.add_content("# Project Guidelines");
101
102        let content = provider.load().await.unwrap();
103        assert_eq!(content.claude_md.len(), 3);
104    }
105
106    #[tokio::test]
107    async fn test_leveled_with_local() {
108        let mut provider = LeveledMemoryProvider::new();
109        provider.add_content("Main content");
110        provider.add_local_content("Local content");
111
112        let content = provider.load().await.unwrap();
113        assert_eq!(content.claude_md.len(), 1);
114        assert_eq!(content.local_md.len(), 1);
115
116        let combined = content.combined_claude_md();
117        assert!(combined.contains("Main content"));
118        assert!(combined.contains("Local content"));
119    }
120}