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
« 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
4SessionStart, SessionEnd event handling
5"""
7from typing import Any, Dict, List
9from lib import HookPayload, HookResult
10from lib.checkpoint import list_checkpoints
11from lib.project import count_specs, get_git_info, get_package_version_info
14def handle_session_start(payload: HookPayload) -> HookResult:
15 """SessionStart event handler with GRACEFUL DEGRADATION
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.
22 Args:
23 payload: Claude Code event payload (cwd key required)
25 Returns:
26 HookResult(system_message=project status summary message)
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
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
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
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)
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)
65 cwd = payload.get("cwd", ".")
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
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
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
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
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"
105 # system_message: displayed directly to the user
106 lines = [
107 "🚀 MoAI-ADK Session Started",
108 "", # Blank line after title
109 ]
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']}")
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']}")
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}")
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}")
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")
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']}%)")
162 system_message = "\n".join(lines)
164 return HookResult(system_message=system_message)
167def handle_session_end(payload: HookPayload) -> HookResult:
168 """SessionEnd event handler (default implementation)"""
169 return HookResult()
172__all__ = ["handle_session_start", "handle_session_end"]