--- phase: 02-daemon-et-i3-bridge plan: 02 type: execute wave: 1 depends_on: [] files_modified: - workspace.go - workspace_test.go - i3bridge.go - i3bridge_test.go - go.mod - go.sum autonomous: true requirements: [I3-01, I3-02] must_haves: truths: - "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" artifacts: - path: "workspace.go" provides: "Resolution PID -> workspace via PPID chain + X11 _NET_WM_PID" exports: ["ReadPPID", "ResolveWorkspace", "BuildTerminalWorkspaceMap"] - path: "i3bridge.go" provides: "Interface I3Client, fuzzy match, switch workspace" exports: ["I3Client", "FuzzyMatch", "SwitchToWorkspace"] key_links: - from: "workspace.go" to: "/proc/PID/status" via: "ReadPPID lit le PPid" pattern: "ReadPPID" - from: "i3bridge.go" to: "go.i3wm.org/i3/v4" via: "GetTree, RunCommand" pattern: "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). @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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: ```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): ```go 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 `. 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 - 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 After completion, create `.planning/phases/02-daemon-et-i3-bridge/02-02-SUMMARY.md`