Coverage for .claude/hooks/moai/lib/session.py: 0.00%

67 statements  

« prev     ^ index     » next       coverage.py v7.11.3, created at 2025-11-19 08:00 +0900

1#!/usr/bin/env python3 

2"""Session event handlers 

3 

4SessionStart, SessionEnd event handling 

5""" 

6 

7from typing import Any, Dict, List 

8 

9from lib import HookPayload, HookResult 

10from lib.checkpoint import list_checkpoints 

11from lib.project import count_specs, get_git_info, get_package_version_info 

12 

13 

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

15 """SessionStart event handler with GRACEFUL DEGRADATION 

16 

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

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

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

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

21 

22 Args: 

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

24 

25 Returns: 

26 HookResult(system_message=project status summary message) 

27 

28 Message Format: 

29 🚀 MoAI-ADK Session Started 

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

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

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

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

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

35 

36 Graceful Degradation Strategy: 

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

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

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

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

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

42 

43 Note: 

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

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

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

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

48 

49 TDD History: 

50 - RED: Session startup message format test 

51 - GREEN: Generate status message by combining helper functions 

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

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

54 - UPDATE: Migrated to Claude Code standard Hook schema 

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

56 

57 """ 

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

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

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

61 if event_phase == "clear": 

62 # Return minimal valid Hook result for clear phase 

63 return HookResult(continue_execution=True) 

64 

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

66 

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

68 git_info = {} 

69 try: 

70 git_info = get_git_info(cwd) 

71 except Exception: 

72 # Graceful degradation - continue without git info 

73 pass 

74 

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

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

77 try: 

78 specs = count_specs(cwd) 

79 except Exception: 

80 # Graceful degradation - continue without spec info 

81 pass 

82 

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

84 checkpoints = [] 

85 try: 

86 checkpoints = list_checkpoints(cwd, max_count=10) 

87 except Exception: 

88 # Graceful degradation - continue without checkpoints 

89 pass 

90 

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

92 version_info = {} 

93 try: 

94 version_info = get_package_version_info() 

95 except Exception: 

96 # Graceful degradation - continue without version info 

97 pass 

98 

99 # Build message with available information 

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

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

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

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

104 

105 # system_message: displayed directly to the user 

106 lines = [ 

107 "🚀 MoAI-ADK Session Started", 

108 "", # Blank line after title 

109 ] 

110 

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

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

113 if version_info.get("update_available"): 

114 # Check if this is a major version update 

115 if version_info.get("is_major_update"): 

116 # Major version warning 

117 lines.append( 

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

119 ) 

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

121 if version_info.get("release_notes_url"): 

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

123 else: 

124 # Regular update 

125 lines.append( 

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

127 ) 

128 if version_info.get("release_notes_url"): 

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

130 

131 # Add upgrade recommendation 

132 if version_info.get("upgrade_command"): 

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

134 else: 

135 # No update available - show current version only 

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

137 

138 

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

140 if git_info: 

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

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

143 

144 # Add last commit message if available 

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

146 if last_commit: 

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

148 

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

150 if checkpoints: 

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

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

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

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

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

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

157 

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

159 if specs["total"] > 0: 

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

161 

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

163 

164 return HookResult(system_message=system_message) 

165 

166 

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

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

169 return HookResult() 

170 

171 

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