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:
@@ -1,5 +1,5 @@
|
||||
// Types REST partagés (préfixe /api/v1) — sous-ensemble P1.
|
||||
import type { SessionSummary } from './protocol.js';
|
||||
// Types REST partagés (préfixe /api/v1).
|
||||
import type { PostCreateHook, RepoSummary, SessionSummary, WorktreeSummary } from './protocol.js';
|
||||
|
||||
export interface ApiError {
|
||||
error: { code: string; message: string; details?: unknown };
|
||||
@@ -34,3 +34,59 @@ export interface SessionResponse {
|
||||
// sont TOUJOURS dérivés du :id (lu sur disque), jamais fournis par le client (cf. spike S1).
|
||||
export type ResumeSessionRequest = Record<string, never>;
|
||||
export type ForkSessionRequest = Record<string, never>;
|
||||
|
||||
// ---- Repos & worktrees (P3) ----
|
||||
export interface ReposListResponse {
|
||||
repos: RepoSummary[];
|
||||
}
|
||||
export interface RepoResponse {
|
||||
repo: RepoSummary;
|
||||
}
|
||||
export interface CreateRepoRequest {
|
||||
path: string;
|
||||
label?: string;
|
||||
postCreateHooks?: PostCreateHook[];
|
||||
preTrust?: boolean;
|
||||
}
|
||||
export interface UpdateRepoRequest {
|
||||
label?: string;
|
||||
postCreateHooks?: PostCreateHook[];
|
||||
preTrust?: boolean;
|
||||
}
|
||||
|
||||
export interface WorktreesListResponse {
|
||||
worktrees: WorktreeSummary[];
|
||||
}
|
||||
export interface WorktreeResponse {
|
||||
worktree: WorktreeSummary;
|
||||
}
|
||||
export interface HookRunResult {
|
||||
hookId: string;
|
||||
label: string;
|
||||
exitCode: number | null;
|
||||
output: string;
|
||||
durationMs: number;
|
||||
}
|
||||
export interface CreateWorktreeRequest {
|
||||
branch: string;
|
||||
/** true : créer la branche (`-b`) ; false : checkout d'une branche existante. */
|
||||
newBranch: boolean;
|
||||
/** point de départ de la nouvelle branche (défaut : HEAD courant du repo). */
|
||||
baseRef?: string;
|
||||
/** chemin cible du worktree (défaut : `<parent>/<repo>-wt-<branch>`). */
|
||||
path?: string;
|
||||
runHooks?: boolean;
|
||||
preTrust?: boolean;
|
||||
/** lancer une session dans le worktree créé (défaut : aucune). */
|
||||
startSession?: 'claude' | 'bash' | null;
|
||||
}
|
||||
export interface CreateWorktreeResponse {
|
||||
worktree: WorktreeSummary;
|
||||
hookResults: HookRunResult[];
|
||||
session: SessionSummary | null;
|
||||
}
|
||||
export interface AdoptWorktreeRequest {
|
||||
path: string;
|
||||
runHooks?: boolean;
|
||||
preTrust?: boolean;
|
||||
}
|
||||
|
||||
@@ -94,6 +94,51 @@ export interface SessionSummary {
|
||||
registryStatus: SessionRegistryStatus | null;
|
||||
}
|
||||
|
||||
// ---- Worktrees & repos (P3) ----
|
||||
/** Hook lancé après création d'un worktree (commande shell exécutée dans le nouveau worktree). */
|
||||
export interface PostCreateHook {
|
||||
id: string;
|
||||
label: string;
|
||||
run: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface RepoSummary {
|
||||
id: string;
|
||||
/** chemin absolu de la racine du repo (main worktree). */
|
||||
path: string;
|
||||
label: string;
|
||||
defaultBranch: string | null;
|
||||
postCreateHooks: PostCreateHook[];
|
||||
/** pré-écrire hasTrustDialogAccepted dans ~/.claude.json à la création d'un worktree. */
|
||||
preTrust: boolean;
|
||||
createdAt: string;
|
||||
/** false si le chemin n'est plus un repo git accessible. */
|
||||
valid: boolean;
|
||||
}
|
||||
|
||||
export interface WorktreeGitStatus {
|
||||
ahead: number;
|
||||
behind: number;
|
||||
dirtyCount: number;
|
||||
upstream: string | null;
|
||||
}
|
||||
|
||||
export interface WorktreeSummary {
|
||||
repoId: string;
|
||||
/** chemin absolu du worktree (clé de corrélation avec le cwd des sessions). */
|
||||
path: string;
|
||||
branch: string | null;
|
||||
head: string;
|
||||
detached: boolean;
|
||||
locked: boolean;
|
||||
prunable: boolean;
|
||||
isMain: boolean;
|
||||
git: WorktreeGitStatus;
|
||||
/** sessions corrélées par cwd (managées + découvertes) ; leur `activity` est remplie en P3-B. */
|
||||
sessions: SessionSummary[];
|
||||
}
|
||||
|
||||
// ---- Messages client → serveur ----
|
||||
export type ClientMessage =
|
||||
| { type: 'hello'; protocol: number }
|
||||
@@ -102,7 +147,7 @@ export type ClientMessage =
|
||||
| { type: 'stdin'; channel: number; data: string }
|
||||
| { type: 'resize'; channel: number; cols: number; rows: number }
|
||||
| { type: 'ack'; channel: number; bytes: number }
|
||||
| { type: 'sub'; topics: Array<'sessions'> }
|
||||
| { type: 'sub'; topics: Array<'sessions' | 'worktrees'> }
|
||||
| { type: 'ping' };
|
||||
|
||||
// ---- Messages serveur → client ----
|
||||
@@ -113,6 +158,10 @@ export type ServerMessage =
|
||||
| { type: 'control_changed'; channel: number; controlling: boolean }
|
||||
| { type: 'session_update'; session: SessionSummary }
|
||||
| { type: 'session_exit'; sessionId: string; exitCode: number | null; signal: number | null }
|
||||
| { type: 'repo_update'; repo: RepoSummary }
|
||||
| { type: 'repo_removed'; repoId: string }
|
||||
| { type: 'worktree_update'; repoId: string; worktree: WorktreeSummary }
|
||||
| { type: 'worktree_removed'; repoId: string; path: string }
|
||||
| { type: 'error'; code: ErrorCode; message: string; channel?: number }
|
||||
| { type: 'pong' };
|
||||
|
||||
@@ -159,8 +208,8 @@ export function parseClientMessage(raw: string): ClientMessage | null {
|
||||
? { type: 'ack', channel: m.channel, bytes: m.bytes }
|
||||
: null;
|
||||
case 'sub':
|
||||
return Array.isArray(m.topics) && m.topics.every((t) => t === 'sessions')
|
||||
? { type: 'sub', topics: m.topics as Array<'sessions'> }
|
||||
return Array.isArray(m.topics) && m.topics.every((t) => t === 'sessions' || t === 'worktrees')
|
||||
? { type: 'sub', topics: m.topics as Array<'sessions' | 'worktrees'> }
|
||||
: null;
|
||||
case 'ping':
|
||||
return { type: 'ping' };
|
||||
|
||||
Reference in New Issue
Block a user