Coverage for src/moai_adk/templates/.claude/hooks/moai/lib/session.py: 9.09%

66 statements  

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

1#!/usr/bin/env python3 

2"""Session event handlers 

3 

4SessionStart, SessionEnd event handling 

5""" 

6 

7 

8from lib import HookPayload, HookResult 

9from lib.checkpoint import list_checkpoints 

10from lib.project import count_specs, get_git_info, get_package_version_info 

11 

12 

13def handle_session_start(payload: HookPayload) -> HookResult: 

14 """SessionStart event handler with GRACEFUL DEGRADATION 

15 

16 When Claude Code Session starts, it displays a summary of project status. 

17 You can check the language, Git status, SPEC progress, and checkpoint list at a glance. 

18 All optional operations are wrapped in try-except to ensure hook completes quickly even if 

19 Git commands, file I/O, or other operations timeout or fail. 

20 

21 Args: 

22 payload: Claude Code event payload (cwd key required) 

23 

24 Returns: 

25 HookResult(system_message=project status summary message) 

26 

27 Message Format: 

28 🚀 MoAI-ADK Session Started 

29 [Version: {version}] - optional if version check fails 

30 [Branch: {branch} ({commit hash})] - optional if git fails 

31 [Changes: {Number of Changed Files}] - optional if git fails 

32 [SPEC Progress: {Complete}/{Total} ({percent}%)] - optional if specs fail 

33 [Checkpoints: {number} available] - optional if checkpoint list fails 

34 

35 Graceful Degradation Strategy: 

36 - OPTIONAL: Version info (skip if timeout/failure) 

37 - OPTIONAL: Git info (skip if timeout/failure) 

38 - OPTIONAL: SPEC progress (skip if timeout/failure) 

39 - OPTIONAL: Checkpoint list (skip if timeout/failure) 

40 - Always display SOMETHING to user, never return empty message 

41 

42 Note: 

43 - Claude Code processes SessionStart in several stages (clear → compact) 

44 - Display message only at "compact" stage to prevent duplicate output 

45 - "clear" step returns minimal result (empty hookSpecificOutput) 

46 - CRITICAL: All optional operations must complete within 2-3 seconds total 

47 

48 TDD History: 

49 - RED: Session startup message format test 

50 - GREEN: Generate status message by combining helper functions 

51 - REFACTOR: Improved message format, improved readability, added checkpoint list 

52 - FIX: Prevent duplicate output of clear step (only compact step is displayed) 

53 - UPDATE: Migrated to Claude Code standard Hook schema 

54 - HOTFIX: Add graceful degradation for timeout scenarios (Issue #66) 

55 

56 """ 

57 # Claude Code SessionStart runs in several stages (clear, compact, etc.) 

58 # Ignore the "clear" stage and output messages only at the "compact" stage 

59 event_phase = payload.get("phase", "") 

60 if event_phase == "clear": 

61 # Return minimal valid Hook result for clear phase 

62 return HookResult(continue_execution=True) 

63 

64 cwd = payload.get("cwd", ".") 

65 

66 # OPTIONAL: Git info - skip if timeout/failure 

67 git_info = {} 

68 try: 

69 git_info = get_git_info(cwd) 

70 except Exception: 

71 # Graceful degradation - continue without git info 

72 pass 

73 

74 # OPTIONAL: SPEC progress - skip if timeout/failure 

75 specs = {"completed": 0, "total": 0, "percentage": 0} 

76 try: 

77 specs = count_specs(cwd) 

78 except Exception: 

79 # Graceful degradation - continue without spec info 

80 pass 

81 

82 # OPTIONAL: Checkpoint list - skip if timeout/failure 

83 checkpoints = [] 

84 try: 

85 checkpoints = list_checkpoints(cwd, max_count=10) 

86 except Exception: 

87 # Graceful degradation - continue without checkpoints 

88 pass 

89 

90 # OPTIONAL: Package version info - skip if timeout/failure 

91 version_info = {} 

92 try: 

93 version_info = get_package_version_info() 

94 except Exception: 

95 # Graceful degradation - continue without version info 

96 pass 

97 

98 # Build message with available information 

99 branch = git_info.get("branch", "N/A") if git_info else "N/A" 

100 commit = git_info.get("commit", "N/A")[:7] if git_info else "N/A" 

101 changes = git_info.get("changes", 0) if git_info else 0 

102 spec_progress = f"{specs['completed']}/{specs['total']}" if specs["total"] > 0 else "0/0" 

103 

104 # system_message: displayed directly to the user 

105 lines = [ 

106 "🚀 MoAI-ADK Session Started", 

107 "", # Blank line after title 

108 ] 

109 

110 # Add version info first (at the top, right after title) 

111 if version_info and version_info.get("current") != "unknown": 

112 if version_info.get("update_available"): 

113 # Check if this is a major version update 

114 if version_info.get("is_major_update"): 

115 # Major version warning 

116 lines.append( 

117 f" ⚠️ Major version update available: {version_info['current']}{version_info['latest']}" 

118 ) 

119 lines.append(" Breaking changes detected. Review release notes:") 

120 if version_info.get("release_notes_url"): 

121 lines.append(f" 📝 {version_info['release_notes_url']}") 

122 else: 

123 # Regular update 

124 lines.append( 

125 f" 🗿 MoAI-ADK Ver: {version_info['current']}{version_info['latest']} available ✨" 

126 ) 

127 if version_info.get("release_notes_url"): 

128 lines.append(f" 📝 Release Notes: {version_info['release_notes_url']}") 

129 

130 # Add upgrade recommendation 

131 if version_info.get("upgrade_command"): 

132 lines.append(f" ⬆️ Upgrade: {version_info['upgrade_command']}") 

133 else: 

134 # No update available - show current version only 

135 lines.append(f" 🗿 MoAI-ADK Ver: {version_info['current']}") 

136 

137 

138 # Add Git info only if available (not degraded) 

139 if git_info: 

140 lines.append(f" 🌿 Branch: {branch} ({commit})") 

141 lines.append(f" 📝 Changes: {changes}") 

142 

143 # Add last commit message if available 

144 last_commit = git_info.get("last_commit", "") 

145 if last_commit: 

146 lines.append(f" 🔨 Last: {last_commit}") 

147 

148 # Add Checkpoint list (show only the latest 3 items) 

149 if checkpoints: 

150 lines.append(f" 🗂️ Checkpoints: {len(checkpoints)} available") 

151 for cp in reversed(checkpoints[-3:]): # Latest 3 items 

152 branch_short = cp["branch"].replace("before-", "") 

153 lines.append(f" 📌 {branch_short}") 

154 lines.append("") # Blank line before restore command 

155 lines.append(" ↩️ Restore: /alfred:0-project restore") 

156 

157 # Add SPEC progress only if available (not degraded) - at the bottom 

158 if specs["total"] > 0: 

159 lines.append(f" 📋 SPEC Progress: {spec_progress} ({specs['percentage']}%)") 

160 

161 system_message = "\n".join(lines) 

162 

163 return HookResult(system_message=system_message) 

164 

165 

166def handle_session_end(payload: HookPayload) -> HookResult: 

167 """SessionEnd event handler (default implementation)""" 

168 return HookResult() 

169 

170 

171__all__ = ["handle_session_start", "handle_session_end"]