--- phase: 03-hook-server plan: 02 type: execute wave: 2 depends_on: ["03-01"] files_modified: - daemon.go - daemon_test.go - display.go - display_test.go autonomous: true requirements: [STATE-03] must_haves: truths: - "Daemon.Start() lance le hook server HTTP sur localhost:3119 en goroutine" - "Le hook server se ferme proprement quand Daemon.Stop() est appele" - "Si le port 3119 est occupe, le daemon continue sans hook server (graceful degradation)" - "Le poll interval passe a 20s quand un hook a ete recu dans les 60 dernieres secondes (per D-01)" - "Le poll revient a 5s si aucun hook depuis 60s" - "vmux list affiche le WaitType entre parentheses apres le state quand non-vide" - "Les transitions d'etat hook mettent a jour le registre immediatement (per D-02)" artifacts: - path: "daemon.go" provides: "startHookServer, hookPort, httpServer, lastHookTime, pollInterval dynamique" - path: "daemon_test.go" provides: "Tests hook server startup, graceful degradation port occupe, poll slowdown" - path: "display.go" provides: "Affichage WaitType dans DisplaySessionInfos" - path: "display_test.go" provides: "Test affichage WaitType" key_links: - from: "daemon.go" to: "hook.go" via: "startHookServer enregistre handleHook" pattern: "HandleFunc.*handleHook" - from: "daemon.go" to: "daemon.go" via: "pollLoop lit pollInterval dynamiquement" pattern: "pollInterval|lastHookTime" - from: "display.go" to: "protocol.go" via: "SessionInfo.WaitType affiche" pattern: "WaitType" --- Integrer le hook server dans le daemon et afficher le WaitType dans la CLI. Purpose: Le hook server devient une goroutine du daemon. Le poll ralentit quand les hooks sont actifs (D-01). Le WaitType est visible dans `vmux list`. Le daemon degrade gracieusement si le port est occupe. Output: daemon.go avec hook server integre, display.go avec WaitType affiche. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-hook-server/03-RESEARCH.md @.planning/phases/03-hook-server/03-01-SUMMARY.md ```go type HookEvent struct { SessionID string `json:"session_id"` TranscriptPath string `json:"transcript_path"` Cwd string `json:"cwd"` HookEventName string `json:"hook_event_name"` NotificationType string `json:"notification_type,omitempty"` Message string `json:"message,omitempty"` Title string `json:"title,omitempty"` LastAssistantMsg string `json:"last_assistant_message,omitempty"` StopHookActive bool `json:"stop_hook_active,omitempty"` ToolName string `json:"tool_name,omitempty"` } func (d *Daemon) handleHook(w http.ResponseWriter, r *http.Request) func (d *Daemon) processHookEvent(event HookEvent) func (r *SessionRegistry) UpdateFromHook(sessionID, state, waitType, cwd string) ``` ```go type SessionInfo struct { // ... existing fields ... WaitType string `json:"wait_type,omitempty"` // "permission", "question", "idle", "" } ``` ```go type Daemon struct { registry *SessionRegistry labels *LabelStore sockPath string procDir string claudeDir string workspaceResolver func(claudePID int) string i3commander I3Commander pollInterval time.Duration stopCh chan struct{} listener net.Listener } func NewDaemon(sockPath, procDir, claudeDir string, labels *LabelStore) *Daemon func (d *Daemon) Start() error func (d *Daemon) Stop() ``` ```go func DisplaySessionInfos(w io.Writer, sessions []SessionInfo, noColor bool, now time.Time) ``` Task 1: Hook server dans Daemon, graceful degradation, poll dynamique daemon.go, daemon_test.go - daemon.go (Daemon struct, Start, Stop, pollLoop, NewDaemon) - hook.go (handleHook, processHookEvent, crees par Plan 01) - daemon_test.go (newTestDaemon, TestDaemonStartStop) - .planning/phases/03-hook-server/03-RESEARCH.md (startHookServer pattern, cohabitation poll/hooks) - TestHookServerStartsWithDaemon: apres Start(), un POST HTTP sur localhost:hookPort/hook retourne 200 - TestHookServerStopsWithDaemon: apres Stop(), le port HTTP n'ecoute plus - TestHookServerPortBusy: si le port est deja occupe (net.Listen avant), le daemon Start() reussit quand meme (graceful degradation) - TestPollSlowdown: apres un hook event, pollInterval() retourne 20s. Apres 60s sans hook, retourne 5s. 1. Ajouter a Daemon struct dans daemon.go : - `hookPort int` (default 3119) - `httpServer *http.Server` - `lastHookTime time.Time` - `mu sync.Mutex` pour proteger lastHookTime 2. Modifier NewDaemon pour initialiser hookPort=3119. 3. Ajouter `func (d *Daemon) startHookServer() error` dans daemon.go : - Creer http.ServeMux, enregistrer "/hook" sur d.handleHook - Creer http.Server avec Addr=127.0.0.1:hookPort, ReadTimeout=5s, WriteTimeout=5s - Tenter net.Listen("tcp", addr). Si erreur, log warning et retourner nil (graceful degradation, pas de crash). - Lancer go d.httpServer.Serve(ln) 4. Modifier Daemon.Start() : appeler d.startHookServer() apres le scan initial, avant les goroutines. Si erreur, continuer sans hooks. 5. Modifier Daemon.Stop() : si d.httpServer != nil, appeler d.httpServer.Close() 6. Modifier processHookEvent (dans hook.go) : ajouter `d.mu.Lock(); d.lastHookTime = time.Now(); d.mu.Unlock()` a la fin de processHookEvent (per D-02, la mise a jour est immediate). 7. Ajouter `func (d *Daemon) currentPollInterval() time.Duration` : - d.mu.Lock/Unlock pour lire lastHookTime - Si time.Since(lastHookTime) < 60s, retourner 20s - Sinon retourner 5s 8. Modifier pollLoop pour utiliser currentPollInterval() au lieu du ticker fixe : - Remplacer le ticker fixe par un time.After(d.currentPollInterval()) dans la boucle select. 9. Pour les tests, modifier newTestDaemon pour assigner hookPort=0 (pas de bind par defaut). Les tests qui testent le hook server assigneront un port dynamique via net.Listen(":0"). NE PAS modifier le format d'affichage (c'est la tache 2). nix-shell --run "go test -v -run 'TestHookServer|TestPollSlowdown' ./..." - grep -q 'hookPort' daemon.go - grep -q 'httpServer' daemon.go - grep -q 'lastHookTime' daemon.go - grep -q 'startHookServer' daemon.go - grep -q 'currentPollInterval' daemon.go - grep -q 'TestHookServerStartsWithDaemon' daemon_test.go - grep -q 'TestHookServerPortBusy' daemon_test.go - grep -q 'TestPollSlowdown' daemon_test.go Le daemon lance le hook server HTTP au demarrage. Le server se ferme proprement au Stop. Si le port est occupe, le daemon continue sans hooks (log warning). Le poll interval passe a 20s quand hooks actifs, revient a 5s sinon. Tous les tests existants + 4 nouveaux passent. Task 2: Affichage WaitType dans vmux list display.go, display_test.go - display.go (DisplaySessionInfos, format actuel) - display_test.go (tests existants) - protocol.go (SessionInfo avec WaitType) - TestDisplayWaitTypePermission: session avec State="Needs Input" et WaitType="permission" affiche "[Needs Input: permission]" - TestDisplayWaitTypeQuestion: session avec WaitType="question" affiche "[Needs Input: question]" - TestDisplayWaitTypeEmpty: session avec WaitType="" affiche "[Needs Input]" (sans le detail) - TestDisplayWorkingNoWaitType: session Working affiche "[Working]" sans WaitType meme si WaitType est non-vide (securite) 1. Modifier DisplaySessionInfos dans display.go : - Quand State=="Needs Input" et WaitType != "", afficher `[Needs Input: permission]` au lieu de `[Needs Input]`. - Le WaitType s'ajoute apres le state, separe par ": ". - Si State != "Needs Input", ne pas afficher le WaitType meme s'il est non-vide. 2. Ajouter les 4 tests dans display_test.go. Utiliser un bytes.Buffer pour capturer la sortie et verifier les patterns. Le format exact est : `[{stateColor}{State}: {WaitType}{resetColor}]` quand WaitType present, `[{stateColor}{State}{resetColor}]` sinon. nix-shell --run "go test -v -run 'TestDisplay' ./..." - grep -q 'WaitType' display.go - grep -q 'TestDisplayWaitTypePermission' display_test.go - grep -q 'TestDisplayWaitTypeQuestion' display_test.go - grep -q 'TestDisplayWaitTypeEmpty' display_test.go `vmux list` affiche le type d'attente entre parentheses apres le state. Les 4 tests passent. Les tests display existants ne regressent pas. nix-shell --run "go test -v -race ./..." nix-shell --run "go vet ./..." - Hook server demarre avec le daemon et se ferme proprement - Graceful degradation si port occupe (le daemon continue) - Poll interval dynamique : 20s quand hooks actifs, 5s sinon (per D-01) - WaitType affiche dans vmux list pour les sessions en attente - Aucune regression sur la suite de tests complete - `go test -race` passe sans data race After completion, create `.planning/phases/03-hook-server/03-02-SUMMARY.md`