diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index ee6c99e..a5d9314 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -28,11 +28,11 @@ Decimal phases appear between their surrounding integers in numeric order.
2. Chaque session affiche son cwd, worktree git et branche git
3. Chaque session affiche son etat : Working, Needs Input ou Idle
4. Chaque session affiche un apercu des dernieres lignes de sortie
-**Plans**: TBD
+**Plans**: 2 plans
Plans:
-- [ ] 01-01: TBD
-- [ ] 01-02: TBD
+- [ ] 01-01-PLAN.md — Scaffolding projet Go + detection des processus Claude via /proc
+- [ ] 01-02-PLAN.md — Parsing JSONL, heuristique d'etat et CLI vmux list
### Phase 2: Daemon et i3 Bridge
**Goal**: L'utilisateur peut switcher vers n'importe quelle session Claude Code en une action via son workspace i3
diff --git a/.planning/phases/01-session-discovery/01-01-PLAN.md b/.planning/phases/01-session-discovery/01-01-PLAN.md
new file mode 100644
index 0000000..c8243f4
--- /dev/null
+++ b/.planning/phases/01-session-discovery/01-01-PLAN.md
@@ -0,0 +1,223 @@
+---
+phase: 01-session-discovery
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - shell.nix
+ - go.mod
+ - types.go
+ - proc.go
+ - proc_test.go
+autonomous: true
+requirements:
+ - DISC-01
+ - DISC-02
+
+must_haves:
+ truths:
+ - "vmux detecte les processus Claude Code actifs via /proc"
+ - "vmux lit le cwd de chaque processus via /proc/PID/cwd"
+ - "vmux encode le cwd en nom de dossier ~/.claude/projects/"
+ artifacts:
+ - path: "shell.nix"
+ provides: "Environnement de dev NixOS avec Go"
+ contains: "go"
+ - path: "go.mod"
+ provides: "Module Go"
+ contains: "module github.com/pieMusic/vmux"
+ - path: "types.go"
+ provides: "Types partages (Process, Session, SessionState)"
+ exports: ["Process", "Session", "SessionState", "Working", "NeedsInput", "Idle"]
+ - path: "proc.go"
+ provides: "Scan /proc pour les processus Claude"
+ exports: ["FindClaudeProcesses", "EncodePath"]
+ - path: "proc_test.go"
+ provides: "Tests unitaires pour proc.go"
+ contains: "TestFindClaudeProcesses"
+ key_links:
+ - from: "proc.go"
+ to: "/proc/*/cmdline"
+ via: "os.ReadFile + strings.Split sur null bytes"
+ pattern: "ReadFile.*cmdline"
+ - from: "proc.go"
+ to: "/proc/*/cwd"
+ via: "os.Readlink"
+ pattern: "Readlink.*cwd"
+---
+
+
+Scaffolding du projet Go et detection des processus Claude Code via /proc.
+
+Purpose: Poser les fondations (module Go, types, environnement NixOS) et implementer la brique de base : trouver les processus Claude actifs avec leur cwd.
+Output: Un module Go fonctionnel avec `FindClaudeProcesses()` teste qui retourne les PID + cwd des processus Claude.
+
+
+
+@$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/01-session-discovery/01-CONTEXT.md
+@.planning/phases/01-session-discovery/01-RESEARCH.md
+
+
+
+
+
+ Task 1: Scaffolding projet + types partages
+ shell.nix, go.mod, types.go
+
+ .planning/phases/01-session-discovery/01-RESEARCH.md
+
+
+Creer 3 fichiers a la racine du projet :
+
+**shell.nix** : environnement NixOS avec `go` et `gopls` :
+```nix
+{ pkgs ? import {} }:
+pkgs.mkShell {
+ buildInputs = with pkgs; [ go gopls ];
+}
+```
+
+**go.mod** : initialiser le module Go :
+```
+module github.com/pieMusic/vmux
+
+go 1.25
+```
+
+**types.go** (package main) : definir les types partages utilises par tous les autres fichiers :
+
+```go
+package main
+
+type SessionState int
+
+const (
+ Working SessionState = iota
+ NeedsInput
+ Idle
+ Unknown
+)
+
+func (s SessionState) String() string // "Working", "Needs Input", "Idle", "Unknown"
+
+type Process struct {
+ PID int
+ Cmd []string // arguments cmdline
+ Cwd string // readlink /proc/PID/cwd
+}
+
+type Session struct {
+ Process Process
+ SessionID string
+ GitBranch string
+ State SessionState
+ Preview string // dernieres lignes de sortie
+ CwdPath string // cwd du processus
+ Worktree string // git worktree (peut differer du cwd)
+}
+```
+
+Le `SessionState` a 4 valeurs : `Working`, `NeedsInput`, `Idle`, `Unknown`.
+`Unknown` est utilise quand aucun JSONL n'est trouve pour un processus (per D-04, pitfall 4 de la recherche).
+
+
+ cd /home/pierre/Code/vibe/vmux && nix-shell --run "go build ./..."
+
+
+ - shell.nix contient `go` et `gopls` dans buildInputs
+ - go.mod contient `module github.com/pieMusic/vmux`
+ - types.go contient `type SessionState int`
+ - types.go contient `Working SessionState = iota`
+ - types.go contient `NeedsInput`
+ - types.go contient `Idle`
+ - types.go contient `Unknown`
+ - types.go contient `type Process struct`
+ - types.go contient `type Session struct`
+ - `go build ./...` compile sans erreur
+
+ Le projet compile. Les types Process, Session et SessionState sont definis et exportes.
+
+
+
+ Task 2: Scan /proc + encoding chemin + tests
+ proc.go, proc_test.go
+
+ types.go
+ .planning/phases/01-session-discovery/01-RESEARCH.md
+
+
+ - TestFindClaudeProcesses: avec un faux /proc contenant 3 repertoires (2 processus "claude", 1 processus "bash"), retourne exactement 2 Process avec les bons PID, Cmd et Cwd.
+ - TestFindClaudeProcesses_PermissionDenied: un /proc/PID/cmdline illisible (permission denied) est ignore silencieusement, pas de crash.
+ - TestFindClaudeProcesses_EmptyProc: un /proc vide retourne un slice vide sans erreur.
+ - TestEncodePath: "/home/pierre/Code/vibe/vmux" -> "-home-pierre-Code-vibe-vmux"
+ - TestEncodePath_DotDir: "/home/pierre/.config/app" -> "-home-pierre--config-app" (les `.` sont remplaces par `-`, d'ou le double dash)
+
+
+**proc.go** (package main) :
+
+`FindClaudeProcesses(procDir string) ([]Process, error)` :
+- Accepte le chemin vers /proc en parametre (permet le test avec un faux /proc). En production, passer "/proc".
+- Lire `os.ReadDir(procDir)`, filtrer les entrees dont le nom est numerique (PID).
+- Pour chaque PID, lire `procDir/PID/cmdline` via `os.ReadFile`. Le contenu est separe par des null bytes (`\x00`). Splitter avec `strings.Split(strings.TrimRight(data, "\x00"), "\x00")`.
+- Si le premier argument (index 0) se termine par `claude` ou est exactement `claude`, c'est un processus Claude Code (per D-01).
+- Lire le cwd via `os.Readlink(procDir/PID/cwd)` (per D-01).
+- Ignorer silencieusement les erreurs de lecture (permission denied, processus disparu) : `continue` dans la boucle.
+- Retourner un `[]Process` avec PID (int parse du nom de dossier), Cmd ([]string des arguments) et Cwd (string du readlink).
+
+`EncodePath(path string) string` :
+- Remplacer tous les `/` par `-` et tous les `.` par `-` (per RESEARCH Pattern 2, pitfall 6).
+- Utiliser `strings.NewReplacer("/", "-", ".", "-").Replace(path)`.
+
+**proc_test.go** (package main) :
+
+Utiliser des fixtures temporaires pour simuler /proc (per strategie TDD de la recherche) :
+- `createFakeProc(t *testing.T, entries map[int]fakeProcEntry) string` : cree un repertoire temporaire avec la structure /proc simulee.
+- `fakeProcEntry` : struct avec `cmdline string` (contenu brut avec \x00) et `cwd string` (chemin cible du symlink).
+- Creer des symlinks pour `cwd` (`os.Symlink`).
+- Creer des fichiers pour `cmdline` (`os.WriteFile`).
+
+5 tests decrits dans la section behavior.
+
+
+ cd /home/pierre/Code/vibe/vmux && nix-shell --run "go test -v -run 'TestFindClaudeProcesses|TestEncodePath' ./..."
+
+
+ - proc.go contient `func FindClaudeProcesses(procDir string) ([]Process, error)`
+ - proc.go contient `func EncodePath(path string) string`
+ - proc_test.go contient `TestFindClaudeProcesses` qui cree un faux /proc
+ - proc_test.go contient `TestEncodePath`
+ - proc_test.go contient `TestEncodePath_DotDir`
+ - Tous les tests passent avec `go test -v -run 'TestFindClaudeProcesses|TestEncodePath' ./...`
+ - FindClaudeProcesses accepte un parametre procDir (pas de /proc en dur)
+
+ FindClaudeProcesses detecte les processus Claude dans un faux /proc. EncodePath convertit correctement les chemins. Tous les tests passent.
+
+
+
+
+
+- `go build ./...` compile sans erreur
+- `go test -v ./...` : tous les tests passent
+- `go vet ./...` : pas de warning
+
+
+
+- Le projet Go est initialise avec shell.nix et go.mod
+- Les types Process, Session, SessionState sont definis
+- FindClaudeProcesses retourne les processus Claude depuis /proc
+- EncodePath encode les chemins en noms de dossiers ~/.claude/projects/
+- Tests unitaires couvrent les cas normaux et les cas limites
+
+
+
diff --git a/.planning/phases/01-session-discovery/01-02-PLAN.md b/.planning/phases/01-session-discovery/01-02-PLAN.md
new file mode 100644
index 0000000..e89d783
--- /dev/null
+++ b/.planning/phases/01-session-discovery/01-02-PLAN.md
@@ -0,0 +1,423 @@
+---
+phase: 01-session-discovery
+plan: 02
+type: execute
+wave: 2
+depends_on:
+ - 01-01
+files_modified:
+ - session.go
+ - session_test.go
+ - state.go
+ - state_test.go
+ - display.go
+ - display_test.go
+ - main.go
+autonomous: true
+requirements:
+ - DISC-03
+ - STATE-01
+ - STATE-02
+
+must_haves:
+ truths:
+ - "vmux affiche la branche git de chaque session"
+ - "vmux detecte si une session travaille, attend input ou est idle"
+ - "vmux affiche un apercu des dernieres lignes de sortie"
+ - "vmux list affiche toutes les sessions avec leur etat, cwd, branche et apercu"
+ artifacts:
+ - path: "session.go"
+ provides: "Matching PID->JSONL, lecture metadonnees JSONL"
+ exports: ["FindSessionForProcess", "TailReadJSONL"]
+ - path: "state.go"
+ provides: "Heuristique d'etat basee sur le dernier message JSONL"
+ exports: ["DetectState", "ExtractPreview"]
+ - path: "display.go"
+ provides: "Formatage et affichage CLI avec couleurs"
+ exports: ["DisplaySessions"]
+ - path: "main.go"
+ provides: "Point d'entree CLI vmux list"
+ contains: "func main"
+ key_links:
+ - from: "session.go"
+ to: "~/.claude/projects//*.jsonl"
+ via: "EncodePath + filepath.Glob"
+ pattern: "EncodePath.*Glob"
+ - from: "state.go"
+ to: "session.go"
+ via: "TailReadJSONL fournit les messages au DetectState"
+ pattern: "DetectState.*JSONLMessage"
+ - from: "main.go"
+ to: "proc.go + session.go + state.go + display.go"
+ via: "Pipeline FindClaudeProcesses -> FindSessionForProcess -> DetectState -> DisplaySessions"
+ pattern: "FindClaudeProcesses.*FindSession.*DetectState.*Display"
+---
+
+
+Parsing JSONL, heuristique d'etat et CLI `vmux list` fonctionnel.
+
+Purpose: Completer le pipeline de detection : pour chaque processus Claude trouve en Plan 01, trouver le JSONL correspondant, determiner l'etat, extraire un apercu, et afficher le tout.
+Output: `vmux list` fonctionnel qui affiche toutes les sessions Claude Code avec etat, cwd, branche git et apercu.
+
+
+
+@$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/01-session-discovery/01-CONTEXT.md
+@.planning/phases/01-session-discovery/01-RESEARCH.md
+@.planning/phases/01-session-discovery/01-01-SUMMARY.md
+
+
+
+
+From types.go:
+```go
+package main
+
+type SessionState int
+const (
+ Working SessionState = iota
+ NeedsInput
+ Idle
+ Unknown
+)
+func (s SessionState) String() string
+
+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:
+```go
+func FindClaudeProcesses(procDir string) ([]Process, error)
+func EncodePath(path string) string
+```
+
+
+
+
+
+
+ Task 1: Matching PID->JSONL + tail-read + heuristique d'etat
+ session.go, session_test.go, state.go, state_test.go
+
+ types.go
+ proc.go
+ .planning/phases/01-session-discovery/01-RESEARCH.md
+
+
+ - TestTailReadJSONL: fichier JSONL de 50 lignes, lire les 3 dernieres -> retourne exactement 3 messages JSON valides.
+ - TestTailReadJSONL_TruncatedLastLine: JSONL dont la derniere ligne est incomplete (pas de \n final) -> ignore la ligne tronquee, retourne les lignes completes precedentes.
+ - TestTailReadJSONL_EmptyFile: fichier vide -> retourne slice vide sans erreur.
+ - TestTailReadJSONL_SmallFile: fichier de 2 lignes, demander 10 -> retourne les 2 lignes.
+ - TestFindSessionForProcess: dossier ~/.claude/projects/ simule avec 2 fichiers JSONL, retourne celui avec le mtime le plus recent.
+ - TestFindSessionForProcess_NoMatch: aucun dossier correspondant -> retourne erreur.
+ - TestDetectState_EndTurnText: message assistant avec stop_reason="end_turn" et content type "text" -> NeedsInput.
+ - TestDetectState_ToolUseAskUser: message assistant avec stop_reason="tool_use" et tool name "AskUserQuestion" -> NeedsInput.
+ - TestDetectState_ToolUseOther: message assistant avec stop_reason="tool_use" et tool name "Read" -> Working.
+ - TestDetectState_Progress: message type="progress" avec data.type="agent_progress" -> Working.
+ - TestDetectState_ToolResult: message type="user" avec content type "tool_result" -> Working.
+ - TestDetectState_IdleThreshold: message timestamp > 60s dans le passe -> Idle.
+ - TestExtractPreview: message assistant avec content text "Voici le resultat..." -> retourne les 3 premieres lignes du texte.
+
+
+**session.go** (package main) :
+
+Structure intermediaire pour le parsing JSONL :
+```go
+type JSONLMessage struct {
+ Type string `json:"type"` // "assistant", "user", "progress", "system"
+ Timestamp string `json:"timestamp"` // ISO 8601
+ SessionID string `json:"sessionId"`
+ Cwd string `json:"cwd"`
+ GitBranch string `json:"gitBranch"`
+ Message *MessagePayload `json:"message,omitempty"`
+ Data *ProgressData `json:"data,omitempty"`
+}
+
+type MessagePayload struct {
+ Role string `json:"role"`
+ Content []ContentBlock `json:"content"`
+ StopReason string `json:"stop_reason"`
+}
+
+type ContentBlock struct {
+ Type string `json:"type"` // "text", "tool_use", "tool_result"
+ Text string `json:"text,omitempty"`
+ Name string `json:"name,omitempty"` // pour tool_use : nom de l'outil
+}
+
+type ProgressData struct {
+ Type string `json:"type"` // "agent_progress", "hook_progress"
+}
+```
+
+`TailReadJSONL(path string, n int) ([]JSONLMessage, error)` :
+- Ouvrir le fichier, `Seek(0, io.SeekEnd)` pour obtenir la taille.
+- Lire des blocs de 8192 octets en reculant depuis la fin.
+- Accumuler les lignes completes (terminees par `\n`). Ignorer la derniere ligne si elle ne se termine pas par `\n` (per pitfall 3 : race condition).
+- Arreter quand on a `n` lignes completes ou qu'on atteint le debut du fichier.
+- Parser chaque ligne en `JSONLMessage` via `json.Unmarshal`. Ignorer les lignes qui ne parsent pas (lignes vides, corruptions).
+- Retourner les messages dans l'ordre chronologique (le plus ancien en premier).
+
+`FindSessionForProcess(claudeDir string, proc Process) (string, []JSONLMessage, error)` :
+- `claudeDir` = chemin vers `~/.claude/projects/` (parametre pour testabilite). En production : `os.Getenv("HOME") + "/.claude/projects/"`.
+- Encoder `proc.Cwd` via `EncodePath(proc.Cwd)` pour obtenir le nom du dossier.
+- Lister les `*.jsonl` dans `claudeDir//` (PAS les sous-dossiers, per pitfall 5 : exclure subagents/).
+- Utiliser `filepath.Glob(filepath.Join(claudeDir, encoded, "*.jsonl"))` puis filtrer ceux qui sont dans un sous-dossier.
+- Trier par mtime (le plus recent en premier). Prendre le premier = session active (per RESEARCH Pattern 5).
+- Appeler `TailReadJSONL(path, 5)` sur le JSONL trouve.
+- Extraire `sessionId` et `gitBranch` depuis la premiere entree retournee.
+- Retourner le chemin JSONL, les messages, et nil. Si aucun JSONL trouve, retourner une erreur.
+
+**state.go** (package main) :
+
+`DetectState(messages []JSONLMessage, now time.Time) SessionState` :
+- Si `messages` est vide, retourner `Unknown`.
+- Prendre le dernier message (index len-1).
+- Si le timestamp du dernier message est > 60 secondes avant `now` -> `Idle` (const `IdleThreshold = 60 * time.Second`).
+- Si `type == "assistant"` et `message.stop_reason == "end_turn"` -> `NeedsInput` (per D-05 : Claude a fini, attend l'utilisateur).
+- Si `type == "assistant"` et `message.stop_reason == "tool_use"` :
+ - Scanner `message.content` pour un `ContentBlock` de type `"tool_use"`.
+ - Si `name == "AskUserQuestion"` -> `NeedsInput` (per D-05).
+ - Sinon -> `Working` (outil en cours d'execution, per D-05).
+- Si `type == "progress"` -> `Working` (per D-05 : hook ou subagent en cours).
+- Si `type == "user"` et un ContentBlock a `type == "tool_result"` -> `Working` (resultat envoye, Claude va repondre).
+- Sinon -> `Unknown`.
+
+Le parametre `now time.Time` permet de tester le seuil Idle sans dependre de l'horloge.
+
+`ExtractPreview(messages []JSONLMessage) string` :
+- Parcourir les messages en ordre inverse pour trouver le dernier `type == "assistant"` avec un `ContentBlock` de type `"text"`.
+- Extraire le champ `text` de ce ContentBlock.
+- Tronquer a 3 lignes (split sur `\n`, prendre les 3 premieres, rejoin).
+- Si le texte depasse 200 caracteres, tronquer a 200 et ajouter "...".
+- Si aucun texte trouve, retourner "" (vide).
+
+**session_test.go** et **state_test.go** :
+
+Utiliser des fixtures JSONL dans des fichiers temporaires. Pour chaque test :
+- Creer un `t.TempDir()`.
+- Ecrire des lignes JSON valides representant les cas decrits dans behavior.
+- Les fixtures JSONL doivent reproduire le format exact documente dans RESEARCH (voir section "Code Examples").
+
+Exemple de fixture pour un message assistant end_turn :
+```json
+{"type":"assistant","timestamp":"2026-03-23T12:00:00Z","sessionId":"abc","cwd":"/tmp","gitBranch":"main","message":{"role":"assistant","content":[{"type":"text","text":"Done."}],"stop_reason":"end_turn"}}
+```
+
+
+ cd /home/pierre/Code/vibe/vmux && nix-shell --run "go test -v -run 'TestTailRead|TestFindSession|TestDetectState|TestExtractPreview' ./..."
+
+
+ - session.go contient `func TailReadJSONL(path string, n int) ([]JSONLMessage, error)`
+ - session.go contient `func FindSessionForProcess(claudeDir string, proc Process) (string, []JSONLMessage, error)`
+ - state.go contient `func DetectState(messages []JSONLMessage, now time.Time) SessionState`
+ - state.go contient `func ExtractPreview(messages []JSONLMessage) string`
+ - state.go contient `IdleThreshold` comme constante a 60 secondes
+ - session_test.go contient `TestTailReadJSONL` et `TestFindSessionForProcess`
+ - state_test.go contient `TestDetectState_EndTurnText`, `TestDetectState_ToolUseAskUser`, `TestDetectState_ToolUseOther`, `TestDetectState_IdleThreshold`
+ - state_test.go contient `TestExtractPreview`
+ - Tous les tests passent
+
+ TailReadJSONL lit les N derniers messages sans charger le fichier entier. FindSessionForProcess fait le matching PID->JSONL. DetectState applique l'heuristique d'etat. ExtractPreview extrait un apercu. Tous les tests passent.
+
+
+
+ Task 2: Affichage CLI + main.go + test d'integration
+ display.go, display_test.go, main.go
+
+ types.go
+ proc.go
+ session.go
+ state.go
+
+
+**display.go** (package main) :
+
+Constantes ANSI (per D-07, pas de lib externe) :
+```go
+const (
+ colorReset = "\033[0m"
+ colorGreen = "\033[32m" // Working
+ colorYellow = "\033[33m" // Needs Input
+ colorRed = "\033[31m" // Needs Input (AskUserQuestion)
+ colorGray = "\033[90m" // Idle / Unknown
+ colorBold = "\033[1m"
+)
+```
+
+`DisplaySessions(w io.Writer, sessions []Session, noColor bool)` :
+- Si `sessions` est vide, ecrire "No active Claude Code sessions found.\n" et retourner.
+- Pour chaque session, afficher un bloc :
+```
+[STATE] cwd (branch)
+ Preview text here...
+```
+- Le `[STATE]` est colore selon l'etat (sauf si `noColor == true`) :
+ - Working -> colorGreen + "Working"
+ - NeedsInput -> colorYellow + "Needs Input"
+ - Idle -> colorGray + "Idle"
+ - Unknown -> colorGray + "Unknown"
+- Le `cwd` est le `Session.CwdPath`. Afficher aussi le `Worktree` si different du cwd.
+- Le `(branch)` est `Session.GitBranch`. Si vide, omettre les parentheses.
+- Le `Preview` est indente de 9 espaces (alignement avec le texte apres [STATE]). Chaque ligne du preview est prefixee de ces espaces.
+- Separer les sessions par une ligne vide.
+- Ecrire dans `w` (io.Writer) pour la testabilite. En production : `os.Stdout`.
+
+`stateColor(state SessionState, noColor bool) string` : helper qui retourne le code ANSI pour un etat, ou "" si noColor.
+
+**display_test.go** (package main) :
+
+- `TestDisplaySessions_WithSessions` : 2 sessions (Working + NeedsInput), verifier que la sortie contient les cwds, branches et etats.
+- `TestDisplaySessions_NoColor` : meme test avec noColor=true, verifier qu'aucun code ANSI n'est present (pas de `\033`).
+- `TestDisplaySessions_Empty` : sessions vide -> "No active Claude Code sessions found."
+
+Utiliser un `bytes.Buffer` comme io.Writer pour capturer la sortie.
+
+**main.go** (package main) :
+
+```go
+func main() {
+ noColor := flag.Bool("no-color", false, "Disable colored output")
+ flag.Parse()
+
+ // Aussi detecter NO_COLOR env var (convention standard)
+ if os.Getenv("NO_COLOR") != "" {
+ *noColor = true
+ }
+
+ args := flag.Args()
+ if len(args) == 0 || args[0] != "list" {
+ fmt.Fprintf(os.Stderr, "Usage: vmux list [--no-color]\n")
+ os.Exit(1)
+ }
+
+ processes, err := FindClaudeProcesses("/proc")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error scanning processes: %v\n", err)
+ os.Exit(1)
+ }
+
+ claudeDir := filepath.Join(os.Getenv("HOME"), ".claude", "projects")
+ now := time.Now()
+ var sessions []Session
+
+ for _, proc := range processes {
+ jsonlPath, messages, err := FindSessionForProcess(claudeDir, proc)
+ if err != nil {
+ // Processus sans JSONL -> afficher avec etat Unknown (per pitfall 4)
+ sessions = append(sessions, Session{
+ Process: proc,
+ State: Unknown,
+ CwdPath: proc.Cwd,
+ })
+ continue
+ }
+
+ state := DetectState(messages, now)
+ preview := ExtractPreview(messages)
+
+ // Extraire metadonnees du JSONL
+ var sessionID, gitBranch, worktree string
+ for _, msg := range messages {
+ if msg.SessionID != "" {
+ sessionID = msg.SessionID
+ }
+ if msg.GitBranch != "" {
+ gitBranch = msg.GitBranch
+ }
+ }
+
+ // Worktree = git rev-parse --show-toplevel depuis le cwd
+ worktree = resolveWorktree(proc.Cwd)
+
+ _ = jsonlPath // utilise pour le debug futur
+
+ sessions = append(sessions, Session{
+ Process: proc,
+ SessionID: sessionID,
+ GitBranch: gitBranch,
+ State: state,
+ Preview: preview,
+ CwdPath: proc.Cwd,
+ Worktree: worktree,
+ })
+ }
+
+ DisplaySessions(os.Stdout, sessions, *noColor)
+}
+
+// resolveWorktree utilise git pour trouver le worktree.
+// Retourne le cwd si git echoue (pas un repo git).
+func resolveWorktree(cwd string) string {
+ cmd := exec.Command("git", "-C", cwd, "rev-parse", "--show-toplevel")
+ out, err := cmd.Output()
+ if err != nil {
+ return cwd
+ }
+ return strings.TrimSpace(string(out))
+}
+```
+
+Imports necessaires : `flag`, `fmt`, `os`, `os/exec`, `path/filepath`, `strings`, `time`.
+
+
+ cd /home/pierre/Code/vibe/vmux && nix-shell --run "go test -v ./... && go build -o vmux ./..."
+
+
+ - display.go contient `func DisplaySessions(w io.Writer, sessions []Session, noColor bool)`
+ - display.go contient les constantes ANSI colorGreen, colorYellow, colorGray
+ - display_test.go contient `TestDisplaySessions_WithSessions`
+ - display_test.go contient `TestDisplaySessions_NoColor`
+ - display_test.go contient `TestDisplaySessions_Empty`
+ - main.go contient `func main()` avec `flag.Parse()` et sous-commande "list"
+ - main.go contient `FindClaudeProcesses("/proc")`
+ - main.go contient `resolveWorktree` qui appelle `git rev-parse --show-toplevel`
+ - main.go gere le flag `--no-color` et la variable `NO_COLOR`
+ - `go build -o vmux ./...` produit un binaire vmux
+ - `go test -v ./...` : tous les tests passent (proc, session, state, display)
+
+ vmux list fonctionne : detecte les processus Claude, lit les JSONL, determine l'etat, affiche le resultat avec couleurs. Le binaire compile. Tous les tests passent.
+
+
+
+
+
+- `go test -v -race ./...` : tous les tests passent sans race condition
+- `go build -o vmux ./...` : le binaire compile
+- `./vmux list` : affiche les sessions Claude Code actives sur le poste (test manuel)
+- `./vmux list --no-color` : meme sortie sans codes ANSI
+
+
+
+- `vmux list` affiche toutes les sessions Claude Code actives (DISC-01)
+- Chaque session affiche son cwd et worktree git (DISC-02)
+- Chaque session affiche sa branche git (DISC-03)
+- Chaque session affiche son etat Working/Needs Input/Idle (STATE-01)
+- Chaque session affiche un apercu de la derniere sortie (STATE-02)
+- Couleurs actives par defaut, desactivables avec --no-color (D-07)
+
+
+