README et CLAUDE.md reflètent désormais la réalité : découverte/reprise, worktrees multi-repo, états fins et dashboard sont livrés et testés. La prochaine phase est P4 (supervision mobile : answer + Web Push + PWA).
81 lines
8.3 KiB
Markdown
81 lines
8.3 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Projet
|
|
|
|
Arboretum : un daemon Node.js unique (`npx git-arboretum`) qui sert un dashboard web pour piloter les worktrees git et les sessions Claude Code qui tournent dessus, depuis n'importe quel appareil. **MVP atteint** : P1 (colonne vertébrale : monorepo, protocole WS, daemon, front, tests, CI), P2 (découverte & reprise des sessions), P3 (worktrees multi-repo, états fins busy/waiting/idle via `claude-adapter`, dashboard worktree-first) sont livrés. La phase en cours est **P4** (« supervision mobile & push » : répondre aux dialogues sans ouvrir le terminal, notifications Web Push, PWA installable). Voir les `VERDICT.md` dans `spikes/` pour les décisions techniques actées avant implémentation.
|
|
|
|
Les commentaires du code et la doc interne sont en **français** ; le README et les messages utilisateur en anglais. Conserver cette convention.
|
|
|
|
## Commandes
|
|
|
|
```bash
|
|
npm run build # build shared → server → web (l'ordre compte, voir ci-dessous)
|
|
npm run typecheck # tsc -b shared + server UNIQUEMENT (le web se typecheck via vue-tsc dans son build)
|
|
npm test # vitest run sur packages/*/test/**/*.test.ts
|
|
npx vitest run packages/server/test/ring-buffer.test.ts # un seul fichier
|
|
npx vitest run -t "flow control" # par nom de test
|
|
npm run dev:server # daemon en watch (tsc -b --watch + node --watch)
|
|
npm run dev:web # Vite ; proxifie /api et /ws vers le daemon sur :7317 (cf. note Origin)
|
|
npm run pack # build + npm pack du tarball git-arboretum
|
|
|
|
node packages/server/scripts/acceptance-p1.mjs # acceptation E2E P1 (daemon réel + client WS réel)
|
|
node packages/server/scripts/acceptance-p2.mjs # acceptation E2E P2 (découverte/reprise : faux ~/.claude + faux binaire claude)
|
|
node packages/server/scripts/acceptance-p3.mjs # acceptation E2E P3 (worktrees : repo git tmp + hook + corrélation session)
|
|
```
|
|
|
|
L'acceptation exige `npm run build` au préalable (elle lance `dist/index.js`) et utilise la commande `bash` plutôt que `claude` pour ne pas consommer de quota.
|
|
|
|
**Node >= 22.16 est requis** (pas seulement recommandé) : la persistance utilise `node:sqlite` (`DatabaseSync`), natif et stable seulement à partir de cette version. `.nvmrc` = 22.
|
|
|
|
## Monorepo (npm workspaces)
|
|
|
|
- **`packages/shared`** (`@arboretum/shared`) — source unique de vérité du protocole WS (`protocol.ts`) et des types REST (`api.ts`), importée par le serveur ET le front. C'est une **dépendance runtime non publiée** : la CI pack les deux tarballs (`@arboretum/shared` + `git-arboretum`) et les installe ensemble.
|
|
- **`packages/server`** (`git-arboretum`) — le daemon Fastify, **le seul paquet publié sur npm** (bin `arboretum`). Embarque la SPA buildée dans `public/` via le hook `prepack` (`scripts/copy-web.mjs`).
|
|
- **`packages/web`** (`@arboretum/web`) — SPA Vue 3 (Pinia, vue-router, xterm.js, Tailwind 4), privée.
|
|
|
|
Ordre de build imposé : `shared` se construit en premier (projets TS `composite` avec references) ; le front doit être buildé avant `prepack` du serveur pour être embarqué.
|
|
|
|
## Architecture serveur
|
|
|
|
`buildApp()` (`packages/server/src/app.ts`) câble tout : `AuthService`, `LoginRateLimiter`, `PtyManager`, les routes REST, la gateway WS, et le service statique de la SPA. `index.ts` est le bin (parse config, ouvre la db, écoute, gère le bootstrap token et le drain au SIGTERM/SIGINT).
|
|
|
|
### Modèle de sécurité (central — ne pas affaiblir)
|
|
Un terminal web est de l'exécution de code à distance par conception. Les garde-fous sont structurants :
|
|
- Bind `127.0.0.1` par défaut ; `config.ts` **refuse** tout bind non-loopback sans `--i-know-this-exposes-a-terminal`. Accès distant recommandé via Tailscale Serve.
|
|
- Un hook `preValidation` global authentifie **toute** requête `/api/**` et `/ws`, et applique un **check Origin strict** quand l'en-tête est présent (anti cross-site WS hijacking — le cookie `SameSite=Strict` ne couvre pas les upgrades). Les routes statiques sont publiques ; les routes publiques explicites portent `config: { public: true }` (ex. login).
|
|
- Tokens stockés **hashés** (sha256), comparaison en temps constant. Le bootstrap token n'est affiché qu'une fois au premier démarrage. Le cookie de session est un payload signé HMAC. Le rate-limit du login est **global** (pas par IP : derrière Tailscale tout arrive de 127.0.0.1) avec backoff exponentiel.
|
|
|
|
### Protocole WebSocket (`packages/shared/src/protocol.ts`)
|
|
Une **seule connexion multiplexée par client**, plusieurs canaux (channels) :
|
|
- Contrôle = frames **texte JSON** ; sortie terminal = frames **binaires** `[type u8][channel u32le][payload]`. Un chunk PTY peut couper un caractère UTF-8 en frontière de frame → le décodage est délégué à `xterm.write(Uint8Array)`, jamais au transport.
|
|
- **Flow control par watermarks** (`FLOW`). Invariant anti-deadlock à préserver absolument : `ACK_EVERY_BYTES <= LOW_WATERMARK`. Le PTY n'est mis en pause que quand **tous** les clients interactifs dépassent `HIGH` ; les observers ne freinent jamais le flux. Un client trop en retard (`LAGGING_BYTES`) est coupé puis resynchronisé.
|
|
- Handshake `hello`/`hello_ok` négocie `PROTOCOL_VERSION`. `parseClientMessage` valide/sanitise tout message entrant (bornes sur dims, longueurs) — toute nouvelle commande client doit y être validée.
|
|
|
|
### PtyManager (`packages/server/src/core/pty-manager.ts`)
|
|
Lance `claude` (ou `bash`) dans node-pty. Sessions vivantes en mémoire (`Map`) ; l'historique est persisté dans la table `sessions` (sqlite). Chaque session a un `RingBuffer` (2 Mo) ; à l'attach, le client reçoit un **resync** = reset terminal + replay de la queue du ring (`REPLAY_TAIL_BYTES`). Outil mono-utilisateur : tout client interactif peut écrire, les observers sont read-only. `kill` envoie SIGTERM puis SIGKILL après un délai de grâce.
|
|
|
|
### claude-launcher (`packages/server/src/core/claude-launcher.ts`)
|
|
Couture volontairement abstraite : Arboretum enveloppe le **CLI `claude` interactif** dans un PTY (pas l'Agent SDK, pas le mode headless). La commande `bash` sert aux tests sans quota. C'est ici que se brancherait un éventuel plan B.
|
|
|
|
### Détails fastify à connaître
|
|
- La route `/ws` est enregistrée **après** le plugin `@fastify/websocket` dans un contexte encapsulé (`app.register(async (scoped) => ...)`) ; sinon le handler reçoit la signature REST `(request, reply)` au lieu de `(socket, req)`.
|
|
- `notFoundHandler` renvoie l'`index.html` de la SPA pour tout ce qui n'est pas `/api/` ou `/ws` (routing client-side).
|
|
|
|
### Base de données (`packages/server/src/db/index.ts`)
|
|
`node:sqlite` en mode WAL. Migrations idempotentes versionnées dans le tableau `MIGRATIONS` + table `schema_migrations`. Tables : `settings`, `auth_tokens`, `sessions`.
|
|
|
|
## Architecture web
|
|
|
|
SPA Vue 3. Un **singleton `wsClient`** (`src/lib/ws-client.ts`) gère l'unique connexion : handshake, reconnexion avec backoff, ré-attache des terminaux ouverts, corrélation FIFO des `attached`, et le **flow control par ACK côté client** (compteur d'octets réellement traités par xterm, remis à zéro à chaque resync, avec un ACK traînant débouncé anti-deadlock). En dev, `vite.config.ts` proxifie `/api` et `/ws` vers le daemon en **réécrivant l'en-tête Origin** vers celle du daemon (sinon le check Origin strict rejette l'origine de Vite). Le garde de routeur vérifie la session via `GET /api/v1/auth/me`.
|
|
|
|
## Spikes & roadmap
|
|
|
|
Les `spikes/sN-*/VERDICT.md` documentent des décisions qui contraignent l'implémentation à venir, notamment :
|
|
- **`--resume` doit toujours s'exécuter dans le cwd d'origine** de la session (lu dans le JSONL).
|
|
- La **vivacité** d'une session se déduit de `pid` + `procStart`, **jamais** de la présence du fichier registre.
|
|
- La distinction d'états fins (busy / waiting / idle) du `claude-adapter` (P3-B) nécessite une lecture d'écran via `@xterm/headless` (le strip ANSI naïf mange les espaces) + le registre `~/.claude/sessions`.
|
|
|
|
Le `SessionRuntimeStatus` du protocole reste `starting`/`running`/`exited` ; les états fins P3-B (`activity`, `waitingFor`, `dialog`) sont des champs **additifs optionnels** de `SessionSummary`, remplis par le `claude-adapter` pour les sessions claude managées (null pour bash / découvertes). P4 ajoutera la commande WS `answer` (répondre aux dialogues) et le Web Push.
|