feat(phase3): add notification system and kugetsu notify command

Phase 3c implementation - Notification System:

### New kugetsu commands:
- `kugetsu notify list` - Show unread notifications from PM Agent
- `kugetsu notify clear` - Mark notifications as read

### Notification system:
- PM Agent writes task events to ~/.kugetsu/notifications.json
- Events: task_complete, task_blocked, task_assigned
- Supports issue_ref and gitea_url for linking
- Hermes/Chat Agent reads notifications on user messages

### kugetsu-pm v2.0:
- Updated documentation with notification behavior
- PM Agent monitors Gitea for task completion
- Two review modes: PM reviews immediately OR asks dev if ready
- Notification triggers documented

### File renamed:
- phase3a-setup.md → kugetsu-chat-setup.md (more descriptive)

### Hermes gateway analysis:
- Gateway is a client (connects to Telegram), not a server
- Cannot push messages directly to Telegram from external process
- Notifications stored locally for Hermes to pick up on next user message
This commit is contained in:
shokollm
2026-03-31 02:19:50 +00:00
parent b3171ed632
commit 3d00ddbc1b
3 changed files with 255 additions and 91 deletions

View File

@@ -6,6 +6,7 @@ SESSIONS_DIR="$KUGETSU_DIR/sessions"
WORKTREES_DIR="$KUGETSU_DIR/worktrees"
REPOS_CONFIG="$KUGETSU_DIR/repos.json"
INDEX_FILE="$KUGETSU_DIR/index.json"
NOTIFICATIONS_FILE="$KUGETSU_DIR/notifications.json"
usage() {
cat << 'EOF'
@@ -18,6 +19,7 @@ Usage:
kugetsu delegate <message> Send message to PM agent
kugetsu status Check kugetsu initialization status
kugetsu doctor [--fix] Diagnose and fix kugetsu issues
kugetsu notify [list|clear] Show or clear notifications
kugetsu list List all tracked sessions
kugetsu prune [--force] Remove orphaned sessions (keeps base + pm-agent)
kugetsu destroy <issue-ref> [-y] Delete session for issue
@@ -39,6 +41,8 @@ Commands:
PM context is loaded once at init time.
status Check if kugetsu is initialized and PM agent is active.
doctor Diagnose kugetsu issues. Use --fix to attempt repairs.
notify Show or clear notifications from PM agent.
Use 'kugetsu notify list' to see unread notifications.
list Show all sessions (base + pm-agent + forked issues).
prune Remove sessions not in index (orphaned from opencode).
Use --force to skip confirmation.
@@ -52,12 +56,18 @@ PM Context:
into the PM agent session at init time. This allows customizing PM
behavior without recreating the session.
Notifications:
PM Agent writes task completion notifications to ~/.kugetsu/notifications.json
Use 'kugetsu notify list' to see unread notifications.
Examples:
kugetsu init
kugetsu status
kugetsu delegate "work on issue #5"
kugetsu doctor
kugetsu doctor --fix
kugetsu notify list
kugetsu notify clear
kugetsu start github.com/shoko/kugetsu#14 "fix bug"
kugetsu continue github.com/shoko/kugetsu#14 "add tests"
kugetsu list
@@ -305,6 +315,143 @@ kugetsu_get_pm_context() {
fi
}
kugetsu_add_notification() {
local type="$1"
local message="$2"
local issue_ref="${3:-}"
local gitea_url="${4:-}"
mkdir -p "$(dirname "$NOTIFICATIONS_FILE")"
local notification=$(python3 << PYEOF
import json
import os
from datetime import datetime
notification = {
"type": "$type",
"message": "$message",
"issue_ref": "$issue_ref" if "$issue_ref" else None,
"gitea_url": "$gitea_url" if "$gitea_url" else None,
"timestamp": datetime.now().isoformat(),
"read": False
}
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
notifications = []
if os.path.exists(file_path):
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
except:
notifications = []
notifications.append(notification)
with open(file_path, 'w') as f:
json.dump(notifications, f, indent=2)
print("Notification added")
PYEOF
)
echo "$notification"
}
kugetsu_get_notifications() {
local limit="${1:-10}"
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
echo "[]"
return
fi
python3 << PYEOF
import json
import os
from datetime import datetime
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
if not os.path.exists(file_path):
print("[]")
exit(0)
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
unread = [n for n in notifications if not n.get("read", False)]
unread.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
for n in unread[:$limit]:
ts = n.get("timestamp", "unknown")
ntype = n.get("type", "info")
msg = n.get("message", "")
issue = n.get("issue_ref", "")
gitea = n.get("gitea_url", "")
print(f"[{ts}] {ntype}: {msg}")
if issue:
print(f" Issue: {issue}")
if gitea:
print(f" Link: {gitea}")
print()
if not unread:
print("No unread notifications.")
except Exception as e:
print(f"Error reading notifications: {e}")
PYEOF
}
kugetsu_clear_notifications() {
if [ ! -f "$NOTIFICATIONS_FILE" ]; then
return
fi
python3 << PYEOF
import json
import os
file_path = os.path.expanduser("$NOTIFICATIONS_FILE")
if not os.path.exists(file_path):
exit(0)
try:
with open(file_path, 'r') as f:
notifications = json.load(f)
for n in notifications:
n["read"] = True
with open(file_path, 'w') as f:
json.dump(notifications, f, indent=2)
print("Notifications marked as read")
except Exception as e:
print(f"Error: {e}")
PYEOF
}
cmd_notify() {
local action="${1:-}"
case "$action" in
""|"list"|"show")
kugetsu_get_notifications 10
;;
"clear")
kugetsu_clear_notifications
;;
*)
echo "Usage: kugetsu notify [list|clear]"
;;
esac
}
cmd_status() {
if [ ! -f "$INDEX_FILE" ]; then
echo "kugetsu_not_initialized"
@@ -978,6 +1125,9 @@ main() {
doctor)
cmd_doctor "$@"
;;
notify)
cmd_notify "$@"
;;
list)
cmd_list "$@"
;;