Files
vmux/.planning/phases/02-daemon-et-i3-bridge/02-01-PLAN.md
2026-03-23 17:37:09 +01:00

10 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
02-daemon-et-i3-bridge 01 execute 1
types.go
protocol.go
protocol_test.go
daemon.go
daemon_test.go
true
DISC-04
STATE-04
truths artifacts key_links
Le daemon maintient un registre des sessions a jour (poll toutes les 5s)
Le daemon ecoute sur un Unix socket et repond aux requetes JSON
Les labels sont persistes dans ~/.vmux/labels.json
Le temps d'attente est tracke quand une session passe a NeedsInput
path provides exports
protocol.go Types Request, Response, SessionInfo pour le protocole socket
Request
Response
SessionInfo
path provides exports
daemon.go SessionRegistry, LabelStore, boucle de poll, Unix socket server
SessionRegistry
LabelStore
StartDaemon
path provides contains
types.go Session enrichi avec Workspace, Label, WaitingSince Workspace string
from to via pattern
daemon.go proc.go FindClaudeProcesses dans la boucle de poll FindClaudeProcesses
from to via pattern
daemon.go protocol.go Request/Response JSON sur le socket Request|Response
Daemon vmuxd : registre des sessions, Unix socket server, labels persistants et tracking du temps d'attente.

Purpose: Transformer vmux d'un CLI one-shot en un daemon persistant qui maintient l'etat des sessions. Output: protocol.go (types IPC), daemon.go (registre + socket + poll + labels), types.go enrichi.

<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/02-daemon-et-i3-bridge/02-CONTEXT.md @.planning/phases/02-daemon-et-i3-bridge/02-RESEARCH.md @.planning/phases/01-session-discovery/01-02-SUMMARY.md

From types.go:

type SessionState int
const (Working SessionState = iota; NeedsInput; Idle; Unknown)
type Process struct { PID int; Cmd []string; Cwd string }
type Session struct { Process Process; SessionID string; GitBranch string; State SessionState; Preview string; CwdPath string; Worktree string }

From proc.go:

func FindClaudeProcesses(procDir string) ([]Process, error)
func EncodePath(path string) string

From session.go:

func FindSessionForProcess(claudeDir string, proc Process) (string, []JSONLMessage, error)
func TailReadJSONL(path string, n int) ([]JSONLMessage, error)

From state.go:

func DetectState(messages []JSONLMessage, now time.Time) SessionState
func ExtractPreview(messages []JSONLMessage) string
Task 1: Protocol types + SessionRegistry + LabelStore protocol.go, protocol_test.go, daemon.go, daemon_test.go, types.go types.go, proc.go, state.go, session.go - TestRequestMarshal: Request{Action:"list"} se serialise/deserialise correctement en JSON - TestResponseWithSessions: Response avec SessionInfo contenant Workspace, Label, WaitingSince - TestRegistryUpdate: Ajouter une session, la retrouver dans List() - TestRegistryWaitingSince: Session passant de Working a NeedsInput enregistre WaitingSince; re-passer a Working la reset - TestRegistryRemoveStale: Sessions absentes du scan sont supprimees du registre - TestLabelStoreSetGet: Set("session-id", "review MR") puis Get("session-id") retourne "review MR" - TestLabelStorePersistence: Set() ecrit sur disque, nouveau LabelStore charge le fichier et retrouve le label - TestLabelStoreLoadMissing: Charger un fichier inexistant retourne un store vide sans erreur 1. Enrichir Session dans types.go (per D-08, D-09): - Ajouter `Workspace string` - Ajouter `Label string` - Ajouter `WaitingSince *time.Time`
2. Creer protocol.go avec les types IPC (per D-01):
   ```go
   type Request struct {
       Action string          `json:"action"` // "list", "switch", "label", "stop"
       Args   json.RawMessage `json:"args,omitempty"`
   }
   type Response struct {
       OK       bool           `json:"ok"`
       Error    string         `json:"error,omitempty"`
       Sessions []SessionInfo  `json:"sessions,omitempty"`
   }
   type SessionInfo struct {
       PID          int        `json:"pid"`
       SessionID    string     `json:"session_id"`
       Cwd          string     `json:"cwd"`
       GitBranch    string     `json:"git_branch"`
       State        string     `json:"state"`
       Preview      string     `json:"preview"`
       Workspace    string     `json:"workspace"`
       Label        string     `json:"label,omitempty"`
       WaitingSince *time.Time `json:"waiting_since,omitempty"`
   }
   type SwitchArgs struct { Query string `json:"query"` }
   type LabelArgs struct { SessionID string `json:"session_id"`; Label string `json:"label"` }
   ```

3. Creer daemon.go avec SessionRegistry (per D-02, D-09):
   ```go
   type TrackedSession struct {
       Info         SessionInfo
       PrevState    string
       WaitingSince time.Time
   }
   type SessionRegistry struct {
       mu       sync.RWMutex
       sessions map[string]*TrackedSession
   }
   func NewRegistry() *SessionRegistry
   func (r *SessionRegistry) Update(info SessionInfo) // track WaitingSince transitions
   func (r *SessionRegistry) List() []SessionInfo
   func (r *SessionRegistry) RemoveStale(activeIDs map[string]bool)
   ```

4. Creer LabelStore dans daemon.go (per D-08):
   ```go
   type LabelStore struct {
       mu     sync.RWMutex
       labels map[string]string
       path   string
   }
   func NewLabelStore(path string) (*LabelStore, error)
   func (ls *LabelStore) Set(sessionID, label string) error
   func (ls *LabelStore) Get(sessionID string) string
   ```
   Persistence dans le fichier JSON. Load au demarrage, save apres chaque Set.
nix-shell --run "go test -run 'TestRequest|TestResponse|TestRegistry|TestLabelStore' -v ./..." - grep -q 'Workspace string' types.go - grep -q 'WaitingSince' types.go - grep -q 'type Request struct' protocol.go - grep -q 'type Response struct' protocol.go - grep -q 'type SessionRegistry struct' daemon.go - grep -q 'type LabelStore struct' daemon.go - grep -q 'func.*LabelStore.*Set' daemon.go - grep -q 'TestRegistryWaitingSince' daemon_test.go - grep -q 'TestLabelStorePersistence' daemon_test.go Protocol types compiles, Registry tracke les transitions WaitingSince, LabelStore persiste sur disque, tous les tests passent. Task 2: Unix socket server + poll loop + stop handler daemon.go, daemon_test.go daemon.go, protocol.go, proc.go, session.go, state.go 1. Ajouter la boucle de poll dans daemon.go (per D-02): ```go func (d *Daemon) scanOnce(procDir, claudeDir string, now time.Time) { // FindClaudeProcesses -> FindSessionForProcess -> DetectState -> registry.Update // resolveWorkspace via d.workspaceResolver (interface, nil-safe pour tests) } ``` Le daemon fait un scan synchrone AVANT d'ecouter sur le socket (per Pitfall 3). Ticker de 5 secondes pour les scans suivants.
2. Ajouter le socket server (per D-01):
   ```go
   type Daemon struct {
       registry   *SessionRegistry
       labels     *LabelStore
       sockPath   string
       procDir    string
       claudeDir  string
       workspaceResolver func(claudePID int) string // nil = pas de workspace
       stopCh     chan struct{}
   }
   func NewDaemon(sockPath, procDir, claudeDir string, labels *LabelStore) *Daemon
   func (d *Daemon) Start() error // scan initial, listen, poll loop
   func (d *Daemon) handleConnection(conn net.Conn) // dispatch Request.Action
   ```
   Gestion du stale socket (per Pitfall 2): tenter net.Dial avant net.Listen, supprimer si stale.
   PID file dans ~/.vmux/vmuxd.pid.

3. Handler pour chaque action:
   - "list": registry.List() avec labels enrichis
   - "label": labels.Set(args.SessionID, args.Label)
   - "stop": fermer le listener, signal stopCh

4. Tests:
   - TestDaemonStartStop: demarrer daemon dans goroutine avec tmpdir socket, envoyer "stop", verifier arret propre
   - TestDaemonListOverSocket: demarrer daemon, envoyer "list", verifier la reponse JSON
   - TestDaemonLabelOverSocket: envoyer "label", puis "list", verifier que le label apparait

Note: Le handler "switch" sera ajoute dans le plan 02-03 (depend de i3bridge du plan 02-02).
Le workspaceResolver est nil dans les tests de ce plan. Le plan 02-02 fournira l'implementation reelle.
nix-shell --run "go test -run 'TestDaemon' -v -race ./..." - grep -q 'type Daemon struct' daemon.go - grep -q 'func.*Daemon.*Start' daemon.go - grep -q 'func.*Daemon.*handleConnection' daemon.go - grep -q 'net.Listen.*unix' daemon.go - grep -q 'TestDaemonStartStop' daemon_test.go - grep -q 'TestDaemonListOverSocket' daemon_test.go Le daemon demarre, ecoute sur un Unix socket, repond a list/label/stop, fait un scan initial synchrone et poll toutes les 5s. Tests avec -race passent. nix-shell --run "go test -v -race ./..." grep -q 'type Daemon struct' daemon.go grep -q 'type LabelStore struct' daemon.go grep -q 'type Request struct' protocol.go

<success_criteria>

  • Protocol types (Request, Response, SessionInfo) definis et testes
  • SessionRegistry tracke les transitions d'etat avec WaitingSince (STATE-04)
  • LabelStore persiste les labels dans ~/.vmux/labels.json (DISC-04)
  • Daemon ecoute sur Unix socket, repond aux requetes list/label/stop (D-01)
  • Boucle de poll toutes les 5s avec scan initial synchrone (D-02)
  • Tous les tests passent avec -race </success_criteria>
After completion, create `.planning/phases/02-daemon-et-i3-bridge/02-01-SUMMARY.md`