Coverage for src / moai_adk / statusline / alfred_detector.py: 38.30%

47 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-20 20:52 +0900

1# type: ignore 

2""" 

3Alfred task detector for statusline 

4 

5""" 

6 

7import json 

8import logging 

9from dataclasses import dataclass 

10from datetime import datetime, timedelta 

11from pathlib import Path 

12from typing import Optional 

13 

14logger = logging.getLogger(__name__) 

15 

16 

17@dataclass 

18class AlfredTask: 

19 """Alfred task information""" 

20 

21 command: Optional[str] 

22 spec_id: Optional[str] 

23 stage: Optional[str] 

24 

25 

26class AlfredDetector: 

27 """Detects active Alfred tasks with 1-second caching""" 

28 

29 # Configuration 

30 _CACHE_TTL_SECONDS = 1 

31 

32 def __init__(self): 

33 """Initialize Alfred detector""" 

34 self._cache: Optional[AlfredTask] = None 

35 self._cache_time: Optional[datetime] = None 

36 self._cache_ttl = timedelta(seconds=self._CACHE_TTL_SECONDS) 

37 self._session_state_path = ( 

38 Path.home() / ".moai" / "memory" / "last-session-state.json" 

39 ) 

40 

41 def detect_active_task(self) -> AlfredTask: 

42 """ 

43 Detect currently active Alfred task 

44 

45 Returns: 

46 AlfredTask with command and spec_id 

47 """ 

48 # Check cache 

49 if self._is_cache_valid(): 

50 return self._cache 

51 

52 # Read and parse session state 

53 task = self._read_session_state() 

54 self._update_cache(task) 

55 return task 

56 

57 def _read_session_state(self) -> AlfredTask: 

58 """ 

59 Read Alfred task from session state file 

60 

61 Returns: 

62 AlfredTask from file or defaults 

63 """ 

64 try: 

65 if not self._session_state_path.exists(): 

66 return self._create_default_task() 

67 

68 with open(self._session_state_path, "r") as f: 

69 data = json.load(f) 

70 

71 active_task = data.get("active_task") 

72 if active_task is None: 

73 return self._create_default_task() 

74 

75 # Parse command and spec_id 

76 command = active_task.get("command") 

77 spec_id = active_task.get("spec_id") 

78 

79 return AlfredTask( 

80 command=command, 

81 spec_id=spec_id, 

82 stage=active_task.get("stage"), 

83 ) 

84 

85 except Exception as e: 

86 logger.debug(f"Error reading Alfred task: {e}") 

87 return self._create_default_task() 

88 

89 def _is_cache_valid(self) -> bool: 

90 """Check if task cache is still valid""" 

91 if self._cache is None or self._cache_time is None: 

92 return False 

93 return datetime.now() - self._cache_time < self._cache_ttl 

94 

95 def _update_cache(self, task: AlfredTask) -> None: 

96 """Update task cache""" 

97 self._cache = task 

98 self._cache_time = datetime.now() 

99 

100 @staticmethod 

101 def _create_default_task() -> AlfredTask: 

102 """Create default task (no active task)""" 

103 return AlfredTask( 

104 command=None, 

105 spec_id=None, 

106 stage=None, 

107 )