P3-A: worktrees multi-repo & cycle de vie (backend)

Enregistrement de repos et gestion de leurs worktrees git, source de vérité
= git (worktrees dérivés + cache court par repo), corrélation worktree ↔
sessions par cwd, mutations sérialisées par repo.

- shared: RepoSummary, PostCreateHook, WorktreeSummary, WorktreeGitStatus ;
  messages WS repo_update/worktree_update/*_removed ; topic sub 'worktrees'
  (+ parseClientMessage) ; DTOs REST repos/worktrees.
- db: migration id:3 (table repos).
- core/git.ts: couche git sûre (execFile, jamais de shell, -- avant chemins,
  GIT_OPTIONAL_LOCKS=0), parseWorktreePorcelain, list/add/remove/prune/status/
  ahead-behind, validation branche + chemin.
- core/claude-trust.ts: pré-trust atomique de ~/.claude.json (spike S3).
- core/worktree-manager.ts: repos CRUD, create (worktree add + pré-trust +
  hooks post-create + startSession optionnel), adopt, delete (garde-fous 409
  dirty / 400 main / 409 session live), prune ; events.
- routes/repos.ts + routes/worktrees.ts, câblage app.ts, gateway topic worktrees.
- tests: git (repos tmp réels), claude-trust, worktree-manager (146 verts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 18:31:06 +02:00
parent fb175bc7c5
commit acd920ebcd
16 changed files with 1208 additions and 10 deletions

View File

@@ -10,8 +10,11 @@ import type { Db } from './db/index.js';
import { AuthService, LoginRateLimiter, type AuthContext } from './auth/service.js';
import { PtyManager } from './core/pty-manager.js';
import { DiscoveryService } from './core/discovery-service.js';
import { WorktreeManager } from './core/worktree-manager.js';
import { registerAuthRoutes } from './routes/auth.js';
import { registerSessionRoutes } from './routes/sessions.js';
import { registerRepoRoutes } from './routes/repos.js';
import { registerWorktreeRoutes } from './routes/worktrees.js';
import { registerWsGateway } from './ws/gateway.js';
declare module 'fastify' {
@@ -28,6 +31,7 @@ export interface AppBundle {
auth: AuthService;
manager: PtyManager;
discovery: DiscoveryService;
worktrees: WorktreeManager;
}
export function buildApp(config: Config, db: Db, serverVersion: string): AppBundle {
@@ -40,6 +44,7 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
projectsDir: config.claudeProjectsDir,
sessionsDir: config.claudeSessionsDir,
});
const worktrees = new WorktreeManager(db, manager, discovery);
void app.register(fastifyCookie);
void app.register(fastifyWebsocket, {
@@ -80,10 +85,12 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
registerAuthRoutes(app, auth, limiter, serverVersion);
registerSessionRoutes(app, manager, discovery);
registerRepoRoutes(app, worktrees);
registerWorktreeRoutes(app, worktrees);
// La route websocket doit être déclarée APRÈS le chargement du plugin (contexte
// encapsulé) — sinon le handler reçoit la signature REST (request, reply).
void app.register(async (scoped) => {
registerWsGateway(scoped, manager, discovery, serverVersion);
registerWsGateway(scoped, manager, discovery, worktrees, serverVersion);
});
// SPA buildée embarquée dans le paquet npm (public/) — absente en dev (vite dev sert le front)
@@ -98,5 +105,5 @@ export function buildApp(config: Config, db: Db, serverVersion: string): AppBund
});
}
return { app, auth, manager, discovery };
return { app, auth, manager, discovery, worktrees };
}