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
« 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
4SessionStart, SessionEnd event handling
5"""
8from lib import HookPayload, HookResult
9from lib.checkpoint import list_checkpoints
10from lib.project import count_specs, get_git_info, get_package_version_info
13def handle_session_start(payload: HookPayload) -> HookResult:
14 """SessionStart event handler with GRACEFUL DEGRADATION
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.
21 Args:
22 payload: Claude Code event payload (cwd key required)
24 Returns:
25 HookResult(system_message=project status summary message)
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
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
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
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)
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)
64 cwd = payload.get("cwd", ".")
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
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
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
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
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"
104 # system_message: displayed directly to the user
105 lines = [
106 "🚀 MoAI-ADK Session Started",
107 "", # Blank line after title
108 ]
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']}")
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']}")
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}")
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}")
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")
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']}%)")
161 system_message = "\n".join(lines)
163 return HookResult(system_message=system_message)
166def handle_session_end(payload: HookPayload) -> HookResult:
167 """SessionEnd event handler (default implementation)"""
168 return HookResult()
171__all__ = ["handle_session_start", "handle_session_end"]