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

9.5 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 02 execute 1
workspace.go
workspace_test.go
i3bridge.go
i3bridge_test.go
go.mod
go.sum
true
I3-01
I3-02
truths artifacts key_links
Un PID Claude Code est resolu vers son workspace i3 via la chaine PPID
Le fuzzy match trouve une session par label, branche ou cwd
Le switch vers un workspace i3 fonctionne via i3 IPC RunCommand
Si i3 ou X11 est absent, le mapping retourne vide sans erreur
path provides exports
workspace.go Resolution PID -> workspace via PPID chain + X11 _NET_WM_PID
ReadPPID
ResolveWorkspace
BuildTerminalWorkspaceMap
path provides exports
i3bridge.go Interface I3Client, fuzzy match, switch workspace
I3Client
FuzzyMatch
SwitchToWorkspace
from to via pattern
workspace.go /proc/PID/status ReadPPID lit le PPid ReadPPID
from to via pattern
i3bridge.go go.i3wm.org/i3/v4 GetTree, RunCommand i3.GetTree|i3.RunCommand
i3 bridge : mapping PID -> workspace via PPID chain + X11, fuzzy match pour switch, abstraction i3 IPC.

Purpose: Permettre a vmux de savoir dans quel workspace i3 se trouve chaque session Claude Code et d'y switcher. Output: workspace.go (PPID chain walk + X11 PID resolution), i3bridge.go (interface i3, fuzzy match, switch).

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

From types.go:

type Process struct { PID int; Cmd []string; Cwd string }

From protocol.go (cree par plan 02-01, mais ce plan n'en depend pas):

type SessionInfo struct {
    PID       int    `json:"pid"`
    Workspace string `json:"workspace"`
    Label     string `json:"label,omitempty"`
    GitBranch string `json:"git_branch"`
    Cwd       string `json:"cwd"`
    // ...
}
Task 1: PPID chain walk + workspace resolution workspace.go, workspace_test.go proc.go, proc_test.go - TestReadPPID: Lire le PPid depuis un faux /proc/PID/status -> retourne le bon PPID - TestReadPPIDMissing: Fichier inexistant -> erreur - TestResolveWorkspace: Claude PID 100 -> PPID 50 -> PPID 10 (dans terminalMap) -> retourne "workspace 3" - TestResolveWorkspaceNotFound: Aucun ancetre dans terminalMap -> retourne "" - TestResolveWorkspaceMaxDepth: Chaine > 20 niveaux -> retourne "" (securite, per Pitfall 7) - TestBuildTerminalWorkspaceMapUnit: Avec un mock i3 tree + mock X11 PID resolver, retourne la bonne map 1. Creer workspace.go:
   ```go
   // ReadPPID lit le PPid depuis /proc/PID/status. procDir injectable pour les tests.
   func ReadPPID(procDir string, pid int) (int, error)
   ```
   Parser la ligne "PPid:\tNNN" du fichier status.

   ```go
   // ResolveWorkspace remonte la chaine PPID depuis claudePID jusqu'a trouver
   // un PID connu dans terminalWorkspaces. Max 20 niveaux (per Pitfall 7).
   func ResolveWorkspace(procDir string, claudePID int, terminalWorkspaces map[int]string) string
   ```

   ```go
   // X11PIDResolver abstrait la lecture de _NET_WM_PID pour testabilite.
   type X11PIDResolver interface {
       GetPID(windowID uint32) (int, error)
   }

   // I3TreeProvider abstrait i3.GetTree() pour testabilite.
   type I3TreeProvider interface {
       GetTree() (*i3.Tree, error)
   }

   // BuildTerminalWorkspaceMap construit la map terminalPID -> workspaceName.
   // Utilise i3 GetTree + X11 _NET_WM_PID (per D-04 corrige par RESEARCH).
   func BuildTerminalWorkspaceMap(tree I3TreeProvider, x11 X11PIDResolver) (map[int]string, error)
   ```
   Walk recursif de l'arbre i3 : pour chaque node avec Window > 0, appeler x11.GetPID(node.Window).
   Tracker le workspace courant via node.Type == i3.WorkspaceNode.

2. Creer l'implementation reelle X11 avec xgbutil (per RESEARCH):
   ```go
   type RealX11Resolver struct { xu *xgbutil.XUtil }
   func NewRealX11Resolver() (*RealX11Resolver, error) // verifie $DISPLAY (per Pitfall 5)
   func (r *RealX11Resolver) GetPID(windowID uint32) (int, error) // ewmh.WmPidGet
   func (r *RealX11Resolver) Close()
   ```

3. Tests avec fake /proc (meme pattern que proc_test.go):
   - Creer des faux fichiers /proc/PID/status avec PPid
   - Mock I3TreeProvider et X11PIDResolver pour tester BuildTerminalWorkspaceMap
   - ReadPPID et ResolveWorkspace testes avec le filesystem reel (tmpdir)

4. Ajouter go.i3wm.org/i3/v4 dans go.mod (per D-06):
   ```bash
   nix-shell --run "go get go.i3wm.org/i3/v4@latest"
   ```
   Et BurntSushi/xgbutil pour ewmh (dependance transitive mais import explicite necessaire).
nix-shell --run "go test -run 'TestReadPPID|TestResolveWorkspace|TestBuildTerminal' -v ./..." - grep -q 'func ReadPPID' workspace.go - grep -q 'func ResolveWorkspace' workspace.go - grep -q 'func BuildTerminalWorkspaceMap' workspace.go - grep -q 'type X11PIDResolver interface' workspace.go - grep -q 'TestResolveWorkspace' workspace_test.go - grep -q 'TestReadPPID' workspace_test.go - grep -q 'go.i3wm.org/i3/v4' go.mod ReadPPID lit le PPid depuis /proc, ResolveWorkspace remonte la chaine PPID avec limite de 20, BuildTerminalWorkspaceMap abstrait i3+X11 derriere des interfaces testables. Tous les tests passent. Task 2: Fuzzy match + switch workspace + i3 client interface i3bridge.go, i3bridge_test.go workspace.go - TestFuzzyMatchByLabel: sessions avec labels, query "review" matche la session avec label "review MR !456" - TestFuzzyMatchByBranch: query "auth" matche la session sur branche "feat/auth-flow" - TestFuzzyMatchByCwd: query "vmux" matche la session avec cwd "/home/pierre/Code/vibe/vmux" - TestFuzzyMatchPriority: session avec label "auth" ET autre session avec branche "auth" -> le label gagne - TestFuzzyMatchNoResult: query "inexistant" retourne nil - TestFuzzyMatchCaseInsensitive: query "AUTH" matche "auth" dans la branche - TestSwitchToWorkspace: Mock i3 RunCommand, verifier que "workspace number 3" est envoye 1. Creer i3bridge.go:
   ```go
   // FuzzyMatch trouve la premiere session matchant query dans : label > branche > cwd.
   // Case-insensitive. Retourne nil si aucun match (per D-07).
   func FuzzyMatch(query string, sessions []SessionInfo) *SessionInfo
   ```
   Priorite : label > GitBranch > Cwd. strings.Contains + strings.ToLower.

   ```go
   // I3Commander abstrait i3.RunCommand pour testabilite.
   type I3Commander interface {
       RunCommand(command string) ([]i3.CommandResult, error)
   }

   // SwitchToWorkspace bascule vers le workspace indique via i3 IPC (per D-06).
   func SwitchToWorkspace(commander I3Commander, wsName string) error
   ```
   Envoie `workspace number <wsName>`. Verifie le Success du resultat.

   ```go
   // RealI3Commander utilise go.i3wm.org/i3/v4 directement.
   type RealI3Commander struct{}
   func (c RealI3Commander) RunCommand(cmd string) ([]i3.CommandResult, error)
   ```

2. Tests :
   - FuzzyMatch : pure logique, pas de mock i3. Construire des []SessionInfo en dur.
   - SwitchToWorkspace : mock I3Commander qui enregistre la commande recue.
nix-shell --run "go test -run 'TestFuzzyMatch|TestSwitchToWorkspace' -v ./..." - grep -q 'func FuzzyMatch' i3bridge.go - grep -q 'func SwitchToWorkspace' i3bridge.go - grep -q 'type I3Commander interface' i3bridge.go - grep -q 'TestFuzzyMatchByLabel' i3bridge_test.go - grep -q 'TestFuzzyMatchPriority' i3bridge_test.go - grep -q 'TestSwitchToWorkspace' i3bridge_test.go FuzzyMatch cherche dans label > branche > cwd (case-insensitive), SwitchToWorkspace envoie la commande i3 IPC. Tests couvrent les 3 niveaux de priorite, le no-match et le case-insensitive. nix-shell --run "go test -v -race ./..." grep -q 'func ResolveWorkspace' workspace.go grep -q 'func FuzzyMatch' i3bridge.go

<success_criteria>

  • Mapping PID -> workspace via PPID chain fonctionnel et teste (I3-01)
  • Fuzzy match sur label/branche/cwd avec priorite correcte (I3-02)
  • Switch workspace via i3 IPC abstrait derriere une interface testable (I3-02)
  • Fallback gracieux si i3/X11 absent (per Pitfall 4, 5)
  • go.i3wm.org/i3/v4 ajoute dans go.mod (D-06)
  • Tous les tests passent avec -race </success_criteria>
After completion, create `.planning/phases/02-daemon-et-i3-bridge/02-02-SUMMARY.md`