Coverage for .claude/hooks/moai/lib/notification.py: 0.00%
83 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"""Notification and control handlers
4Notification, Stop, SubagentStop event handling
5"""
7import json
8from datetime import datetime
9from pathlib import Path
10from typing import Any, Dict
12from lib import HookPayload, HookResult
15def _get_command_state_file(cwd: str) -> Path:
16 """Get the path to command state tracking file"""
17 state_dir = Path(cwd) / ".moai" / "memory"
18 state_dir.mkdir(parents=True, exist_ok=True)
19 return state_dir / "command-execution-state.json"
22def _load_command_state(cwd: str) -> Dict[str, Any]:
23 """Load current command execution state"""
24 try:
25 state_file = _get_command_state_file(cwd)
26 if state_file.exists():
27 with open(state_file, "r", encoding="utf-8") as f:
28 return json.load(f)
29 except Exception:
30 pass
31 return {"last_command": None, "last_timestamp": None, "is_running": False}
34def _save_command_state(cwd: str, state: Dict[str, Any]) -> None:
35 """Save command execution state"""
36 try:
37 state_file = _get_command_state_file(cwd)
38 with open(state_file, "w", encoding="utf-8") as f:
39 json.dump(state, f, indent=2)
40 except Exception:
41 pass
44def _is_duplicate_command(current_cmd: str, last_cmd: str, last_timestamp: str) -> bool:
45 """Check if current command is a duplicate of the last one within 3 seconds"""
46 if not last_cmd or not last_timestamp or current_cmd != last_cmd:
47 return False
49 try:
50 last_time = datetime.fromisoformat(last_timestamp)
51 current_time = datetime.now()
52 time_diff = (current_time - last_time).total_seconds()
53 # Consider it a duplicate if same command within 3 seconds
54 return time_diff < 3
55 except Exception:
56 return False
59def handle_notification(payload: HookPayload) -> HookResult: # type: ignore[return]
60 """Notification event handler
62 Detects and warns about duplicate command executions
63 (When the same /alfred: command is triggered multiple times within 3 seconds)
64 """
65 cwd = payload.get("cwd", ".")
66 notification = payload.get("notification", {})
68 # Extract command information from notification
69 current_cmd = None
70 if isinstance(notification, dict):
71 # Check if notification contains command information
72 text = notification.get("text", "") or str(notification)
73 if "/alfred:" in text:
74 # Extract /alfred: command
75 import re
77 match = re.search(r"/alfred:\S+", text)
78 if match:
79 current_cmd = match.group()
81 if not current_cmd:
82 return HookResult()
84 # Load current state
85 state = _load_command_state(cwd)
86 last_cmd = state.get("last_command")
87 last_timestamp = state.get("last_timestamp")
89 # Check for duplicate
90 if _is_duplicate_command(current_cmd, last_cmd, last_timestamp):
91 warning_msg = (
92 f"⚠️ Duplicate command detected: '{current_cmd}' "
93 f"is running multiple times within 3 seconds.\n"
94 f"This may indicate a system issue. Check logs in `.moai/logs/command-invocations.log`"
95 )
97 # Update state - mark as duplicate detected
98 state["duplicate_detected"] = True
99 state["duplicate_command"] = current_cmd
100 state["duplicate_timestamp"] = datetime.now().isoformat()
101 _save_command_state(cwd, state)
103 return HookResult(system_message=warning_msg, continue_execution=True)
105 # Update state with current command
106 state["last_command"] = current_cmd
107 state["last_timestamp"] = datetime.now().isoformat()
108 state["is_running"] = True
109 state["duplicate_detected"] = False
110 _save_command_state(cwd, state)
112 return HookResult()
115def handle_stop(payload: HookPayload) -> HookResult:
116 """Stop event handler
118 Marks command execution as complete
119 """
120 cwd = payload.get("cwd", ".")
121 state = _load_command_state(cwd)
122 state["is_running"] = False
123 state["last_timestamp"] = datetime.now().isoformat()
124 _save_command_state(cwd, state)
126 return HookResult()
129def handle_subagent_stop(payload: HookPayload) -> HookResult:
130 """SubagentStop event handler
132 Records when a sub-agent finishes execution
133 """
134 cwd = payload.get("cwd", ".")
136 # Extract subagent name if available
137 subagent_name = (
138 payload.get("subagent", {}).get("name")
139 if isinstance(payload.get("subagent"), dict)
140 else None
141 )
143 try:
144 state_file = _get_command_state_file(cwd).parent / "subagent-execution.log"
145 timestamp = datetime.now().isoformat()
147 with open(state_file, "a", encoding="utf-8") as f:
148 f.write(f"{timestamp} | Subagent Stop | {subagent_name}\n")
149 except Exception:
150 pass
152 return HookResult()
155__all__ = ["handle_notification", "handle_stop", "handle_subagent_stop"]