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

1"""Orchestrator module for session_start hook 

2 

3Coordinates overall hook execution, timeout management, and result formatting. 

4 

5Responsibilities: 

6- Manage hook execution lifecycle 

7- Handle timeouts gracefully 

8- Coordinate all cleanup and analysis operations 

9- Format and output hook results 

10""" 

11 

12import json 

13import logging 

14import signal 

15import time 

16from datetime import datetime 

17from typing import Any, Dict, Optional 

18 

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) 

32 

33logger = logging.getLogger(__name__) 

34 

35 

36class OrchestratorError(Exception): 

37 """Exception raised for orchestrator-related errors""" 

38 

39 pass 

40 

41 

42def execute_cleanup_sequence(config: Dict[str, Any]) -> Dict[str, Any]: 

43 """Execute complete cleanup sequence 

44 

45 Args: 

46 config: Configuration dictionary 

47 

48 Returns: 

49 Cleanup results dictionary 

50 

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 } 

64 

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) 

69 

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 

74 

75 # Update cleanup timestamp in config 

76 from pathlib import Path 

77 

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) 

82 

83 config_data["auto_cleanup"]["last_cleanup"] = ( 

84 datetime.now().strftime("%Y-%m-%d") 

85 ) 

86 

87 with open(config_file, "w", encoding="utf-8") as f: 

88 json.dump(config_data, f, indent=2, ensure_ascii=False) 

89 

90 # Update cleanup statistics 

91 update_cleanup_stats(cleanup_stats) 

92 result["cleanup_completed"] = True 

93 

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 

100 

101 return result 

102 

103 

104def execute_analysis_sequence(config: Dict[str, Any]) -> Dict[str, Any]: 

105 """Execute daily analysis sequence 

106 

107 Args: 

108 config: Configuration dictionary 

109 

110 Returns: 

111 Analysis results dictionary 

112 

113 Raises: 

114 OrchestratorError: If sequence execution fails 

115 """ 

116 result = {"analysis_completed": False, "report_path": None} 

117 

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 

125 

126 except Exception as e: 

127 logger.error(f"Analysis sequence failed: {e}") 

128 raise OrchestratorError(f"Analysis sequence failed: {e}") from e 

129 

130 return result 

131 

132 

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 

142 

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 

150 

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 } 

160 

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" 

174 

175 return result 

176 

177 

178def handle_timeout( 

179 graceful_degradation: bool = True, 

180) -> Dict[str, Any]: 

181 """Handle hook timeout 

182 

183 Args: 

184 graceful_degradation: Whether to continue despite timeout 

185 

186 Returns: 

187 Timeout error result 

188 

189 Raises: 

190 TimeoutError: If graceful degradation is disabled 

191 """ 

192 error_msg = "Hook execution timeout" 

193 

194 if not graceful_degradation: 

195 raise TimeoutError(error_msg) 

196 

197 return format_hook_result( 

198 success=False, 

199 execution_time=0, 

200 error=error_msg, 

201 graceful_degradation=graceful_degradation, 

202 ) 

203 

204 

205def main() -> None: 

206 """Main hook execution function 

207 

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 

214 

215 Outputs JSON result to stdout. 

216 """ 

217 graceful_degradation = False 

218 try: 

219 start_time = time.time() 

220 

221 # Load timeout settings 

222 timeout_ms = load_hook_timeout() 

223 timeout_seconds = timeout_ms / 1000 

224 graceful_degradation = get_graceful_degradation() 

225 

226 # Setup timeout handler 

227 def timeout_handler(signum, frame) -> None: 

228 raise TimeoutError("Hook execution timeout") 

229 

230 signal.signal(signal.SIGALRM, timeout_handler) 

231 signal.alarm(int(timeout_seconds)) 

232 

233 try: 

234 # Load configuration 

235 config = load_config() 

236 

237 cleanup_stats = { 

238 "total_cleaned": 0, 

239 "reports_cleaned": 0, 

240 "cache_cleaned": 0, 

241 "temp_cleaned": 0, 

242 } 

243 report_path = None 

244 

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}") 

251 

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}") 

258 

259 # Calculate execution time 

260 execution_time = time.time() - start_time 

261 

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 ) 

269 

270 print(json.dumps(result, ensure_ascii=False, indent=2)) 

271 

272 finally: 

273 signal.alarm(0) # Disable timeout 

274 

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)) 

285 

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)) 

296 

297 

298if __name__ == "__main__": 

299 main()