Coverage for src / moai_adk / hooks / session_start / orchestrator.py: 0.00%
103 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-20 20:52 +0900
1"""Orchestrator module for session_start hook
3Coordinates overall hook execution, timeout management, and result formatting.
5Responsibilities:
6- Manage hook execution lifecycle
7- Handle timeouts gracefully
8- Coordinate all cleanup and analysis operations
9- Format and output hook results
10"""
12import json
13import logging
14import signal
15import time
16from datetime import datetime
17from typing import Any, Dict, Optional
19from moai_adk.hooks.session_start.analysis_report import generate_daily_analysis
20from moai_adk.hooks.session_start.core_cleanup import (
21 CleanupError,
22 cleanup_old_files,
23 update_cleanup_stats,
24)
25from moai_adk.hooks.session_start.state_cleanup import (
26 StateError,
27 get_graceful_degradation,
28 load_config,
29 load_hook_timeout,
30 should_cleanup_today,
31)
33logger = logging.getLogger(__name__)
36class OrchestratorError(Exception):
37 """Exception raised for orchestrator-related errors"""
39 pass
42def execute_cleanup_sequence(config: Dict[str, Any]) -> Dict[str, Any]:
43 """Execute complete cleanup sequence
45 Args:
46 config: Configuration dictionary
48 Returns:
49 Cleanup results dictionary
51 Raises:
52 OrchestratorError: If sequence execution fails
53 """
54 result = {
55 "cleanup_completed": False,
56 "cleanup_stats": {
57 "total_cleaned": 0,
58 "reports_cleaned": 0,
59 "cache_cleaned": 0,
60 "temp_cleaned": 0,
61 },
62 "report_path": None,
63 }
65 try:
66 # Check if cleanup is needed
67 last_cleanup = config.get("auto_cleanup", {}).get("last_cleanup")
68 cleanup_days = config.get("auto_cleanup", {}).get("cleanup_days", 7)
70 if should_cleanup_today(last_cleanup, cleanup_days):
71 # Perform cleanup
72 cleanup_stats = cleanup_old_files(config)
73 result["cleanup_stats"] = cleanup_stats
75 # Update cleanup timestamp in config
76 from pathlib import Path
78 config_file = Path(".moai/config/config.json")
79 if config_file.exists():
80 with open(config_file, "r", encoding="utf-8") as f:
81 config_data = json.load(f)
83 config_data["auto_cleanup"]["last_cleanup"] = (
84 datetime.now().strftime("%Y-%m-%d")
85 )
87 with open(config_file, "w", encoding="utf-8") as f:
88 json.dump(config_data, f, indent=2, ensure_ascii=False)
90 # Update cleanup statistics
91 update_cleanup_stats(cleanup_stats)
92 result["cleanup_completed"] = True
94 except (CleanupError, StateError) as e:
95 logger.error(f"Cleanup sequence failed: {e}")
96 raise OrchestratorError(f"Cleanup sequence failed: {e}") from e
97 except Exception as e:
98 logger.error(f"Unexpected error in cleanup sequence: {e}")
99 raise OrchestratorError(f"Cleanup sequence failed: {e}") from e
101 return result
104def execute_analysis_sequence(config: Dict[str, Any]) -> Dict[str, Any]:
105 """Execute daily analysis sequence
107 Args:
108 config: Configuration dictionary
110 Returns:
111 Analysis results dictionary
113 Raises:
114 OrchestratorError: If sequence execution fails
115 """
116 result = {"analysis_completed": False, "report_path": None}
118 try:
119 # Check if analysis should run (daily)
120 last_analysis = config.get("daily_analysis", {}).get("last_analysis")
121 if should_cleanup_today(last_analysis, 1): # Run daily
122 report_path = generate_daily_analysis(config)
123 result["report_path"] = report_path
124 result["analysis_completed"] = report_path is not None
126 except Exception as e:
127 logger.error(f"Analysis sequence failed: {e}")
128 raise OrchestratorError(f"Analysis sequence failed: {e}") from e
130 return result
133def format_hook_result(
134 success: bool,
135 execution_time: float,
136 cleanup_stats: Optional[Dict[str, int]] = None,
137 report_path: Optional[str] = None,
138 error: Optional[str] = None,
139 graceful_degradation: bool = True,
140) -> Dict[str, Any]:
141 """Format hook execution result as JSON
143 Args:
144 success: Whether execution was successful
145 execution_time: Total execution time in seconds
146 cleanup_stats: Cleanup statistics (if applicable)
147 report_path: Path to generated report (if applicable)
148 error: Error message (if failed)
149 graceful_degradation: Whether graceful degradation is enabled
151 Returns:
152 Formatted result dictionary
153 """
154 result = {
155 "hook": "session_start__auto_cleanup",
156 "success": success,
157 "execution_time_seconds": round(execution_time, 3),
158 "timestamp": datetime.now().isoformat(),
159 }
161 if success:
162 result["cleanup_stats"] = cleanup_stats or {
163 "total_cleaned": 0,
164 "reports_cleaned": 0,
165 "cache_cleaned": 0,
166 "temp_cleaned": 0,
167 }
168 result["daily_analysis_report"] = report_path
169 else:
170 result["error"] = error or "Unknown error"
171 result["graceful_degradation"] = graceful_degradation
172 if graceful_degradation:
173 result["message"] = "Hook failed but continuing due to graceful degradation"
175 return result
178def handle_timeout(
179 graceful_degradation: bool = True,
180) -> Dict[str, Any]:
181 """Handle hook timeout
183 Args:
184 graceful_degradation: Whether to continue despite timeout
186 Returns:
187 Timeout error result
189 Raises:
190 TimeoutError: If graceful degradation is disabled
191 """
192 error_msg = "Hook execution timeout"
194 if not graceful_degradation:
195 raise TimeoutError(error_msg)
197 return format_hook_result(
198 success=False,
199 execution_time=0,
200 error=error_msg,
201 graceful_degradation=graceful_degradation,
202 )
205def main() -> None:
206 """Main hook execution function
208 Executes the complete session_start hook lifecycle:
209 1. Load configuration
210 2. Setup timeout handler
211 3. Execute cleanup sequence
212 4. Execute analysis sequence
213 5. Format and output results
215 Outputs JSON result to stdout.
216 """
217 graceful_degradation = False
218 try:
219 start_time = time.time()
221 # Load timeout settings
222 timeout_ms = load_hook_timeout()
223 timeout_seconds = timeout_ms / 1000
224 graceful_degradation = get_graceful_degradation()
226 # Setup timeout handler
227 def timeout_handler(signum, frame) -> None:
228 raise TimeoutError("Hook execution timeout")
230 signal.signal(signal.SIGALRM, timeout_handler)
231 signal.alarm(int(timeout_seconds))
233 try:
234 # Load configuration
235 config = load_config()
237 cleanup_stats = {
238 "total_cleaned": 0,
239 "reports_cleaned": 0,
240 "cache_cleaned": 0,
241 "temp_cleaned": 0,
242 }
243 report_path = None
245 # Execute cleanup sequence
246 try:
247 cleanup_result = execute_cleanup_sequence(config)
248 cleanup_stats = cleanup_result["cleanup_stats"]
249 except OrchestratorError as e:
250 logger.warning(f"Cleanup sequence skipped: {e}")
252 # Execute analysis sequence
253 try:
254 analysis_result = execute_analysis_sequence(config)
255 report_path = analysis_result["report_path"]
256 except OrchestratorError as e:
257 logger.warning(f"Analysis sequence skipped: {e}")
259 # Calculate execution time
260 execution_time = time.time() - start_time
262 # Format and output success result
263 result = format_hook_result(
264 success=True,
265 execution_time=execution_time,
266 cleanup_stats=cleanup_stats,
267 report_path=report_path,
268 )
270 print(json.dumps(result, ensure_ascii=False, indent=2))
272 finally:
273 signal.alarm(0) # Disable timeout
275 except TimeoutError as e:
276 # Handle timeout
277 execution_time = time.time() - start_time
278 result = format_hook_result(
279 success=False,
280 execution_time=execution_time,
281 error=f"Hook execution timeout: {str(e)}",
282 graceful_degradation=graceful_degradation,
283 )
284 print(json.dumps(result, ensure_ascii=False, indent=2))
286 except Exception as e:
287 # Handle unexpected exceptions
288 execution_time = time.time() - start_time
289 result = format_hook_result(
290 success=False,
291 execution_time=execution_time,
292 error=f"Hook execution failed: {str(e)}",
293 graceful_degradation=graceful_degradation,
294 )
295 print(json.dumps(result, ensure_ascii=False, indent=2))
298if __name__ == "__main__":
299 main()