Files
vmux/.planning/phases/03-hook-server/03-02-PLAN.md
2026-03-23 19:36:15 +01:00

9.9 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
03-hook-server 02 execute 2
03-01
daemon.go
daemon_test.go
display.go
display_test.go
true
STATE-03
truths artifacts key_links
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)
path provides
daemon.go startHookServer, hookPort, httpServer, lastHookTime, pollInterval dynamique
path provides
daemon_test.go Tests hook server startup, graceful degradation port occupe, poll slowdown
path provides
display.go Affichage WaitType dans DisplaySessionInfos
path provides
display_test.go Test affichage WaitType
from to via pattern
daemon.go hook.go startHookServer enregistre handleHook HandleFunc.*handleHook
from to via pattern
daemon.go daemon.go pollLoop lit pollInterval dynamiquement pollInterval|lastHookTime
from to via pattern
display.go protocol.go SessionInfo.WaitType affiche 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.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.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)


<!-- From protocol.go (enriched by Plan 01) -->
```go
type SessionInfo struct {
    // ... existing fields ...
    WaitType string `json:"wait_type,omitempty"` // "permission", "question", "idle", ""
}
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()
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, hook.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 ./..."

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/03-hook-server/03-02-SUMMARY.md`