Coverage for src / osiris_cli / context_manager.py: 0%

87 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2025-12-27 17:41 +0200

1#!/usr/bin/env python3 

2""" 

3Context File Manager for Osiris CLI 

4Automatically loads and manages project context files 

5""" 

6 

7from pathlib import Path 

8from typing import List, Dict, Optional 

9from rich.console import Console 

10import fnmatch 

11 

12console = Console() 

13 

14 

15class ContextManager: 

16 """Manage project context files""" 

17 

18 def __init__(self): 

19 self.contexts: Dict[str, str] = {} 

20 self.auto_load_patterns = [ 

21 "OSIRIS.md", 

22 "README.md", 

23 ".osiris/context.md", 

24 "docs/CONTEXT.md", 

25 ] 

26 

27 def auto_load(self, directory: Path = None) -> int: 

28 """Auto-load context files from directory""" 

29 if directory is None: 

30 directory = Path.cwd() 

31 

32 loaded = 0 

33 

34 # Try each pattern 

35 for pattern in self.auto_load_patterns: 

36 file_path = directory / pattern 

37 if file_path.exists() and file_path.is_file(): 

38 try: 

39 with open(file_path, 'r') as f: 

40 content = f.read() 

41 self.contexts[str(file_path)] = content 

42 console.print(f"[green]✓[/green] Loaded context: {file_path.name}") 

43 loaded += 1 

44 except Exception as e: 

45 console.print(f"[yellow]Warning: Could not load {file_path}: {e}[/yellow]") 

46 

47 return loaded 

48 

49 def add_file(self, file_path: Path) -> bool: 

50 """Add a specific context file""" 

51 if not file_path.exists(): 

52 console.print(f"[red]Error: File not found: {file_path}[/red]") 

53 return False 

54 

55 try: 

56 with open(file_path, 'r') as f: 

57 content = f.read() 

58 self.contexts[str(file_path)] = content 

59 console.print(f"[green]✓[/green] Added context: {file_path.name}") 

60 return True 

61 except Exception as e: 

62 console.print(f"[red]Error loading {file_path}: {e}[/red]") 

63 return False 

64 

65 def add_directory(self, directory: Path, pattern: str = "*.md") -> int: 

66 """Add all matching files from directory""" 

67 loaded = 0 

68 

69 for file_path in directory.rglob(pattern): 

70 if file_path.is_file(): 

71 if self.add_file(file_path): 

72 loaded += 1 

73 

74 return loaded 

75 

76 def remove(self, file_path: str) -> bool: 

77 """Remove a context file""" 

78 if file_path in self.contexts: 

79 del self.contexts[file_path] 

80 console.print(f"[green]✓[/green] Removed context: {Path(file_path).name}") 

81 return True 

82 else: 

83 console.print(f"[yellow]Context not found: {file_path}[/yellow]") 

84 return False 

85 

86 def clear(self): 

87 """Clear all contexts""" 

88 self.contexts.clear() 

89 console.print("[green]✓[/green] All contexts cleared") 

90 

91 def list_contexts(self) -> List[str]: 

92 """List all loaded contexts""" 

93 return list(self.contexts.keys()) 

94 

95 def get_combined_context(self) -> str: 

96 """Get all contexts combined""" 

97 if not self.contexts: 

98 return "" 

99 

100 combined = "# Project Context\n\n" 

101 

102 for file_path, content in self.contexts.items(): 

103 file_name = Path(file_path).name 

104 combined += f"## {file_name}\n\n{content}\n\n" 

105 

106 return combined 

107 

108 def get_context_summary(self) -> str: 

109 """Get summary of loaded contexts""" 

110 if not self.contexts: 

111 return "No context files loaded" 

112 

113 total_chars = sum(len(content) for content in self.contexts.values()) 

114 total_lines = sum(content.count('\n') for content in self.contexts.values()) 

115 

116 summary = f"{len(self.contexts)} file(s) loaded\n" 

117 summary += f"Total: {total_chars:,} characters, {total_lines:,} lines\n\n" 

118 

119 for file_path in self.contexts.keys(): 

120 file_name = Path(file_path).name 

121 char_count = len(self.contexts[file_path]) 

122 summary += f"{file_name} ({char_count:,} chars)\n" 

123 

124 return summary 

125 

126 def search(self, query: str) -> List[tuple]: 

127 """Search across all contexts""" 

128 results = [] 

129 query_lower = query.lower() 

130 

131 for file_path, content in self.contexts.items(): 

132 lines = content.split('\n') 

133 for i, line in enumerate(lines, 1): 

134 if query_lower in line.lower(): 

135 results.append((Path(file_path).name, i, line.strip())) 

136 

137 return results 

138 

139 

140# Global context manager 

141context_manager = ContextManager()