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).
42 lines
1.9 KiB
TypeScript
42 lines
1.9 KiB
TypeScript
// Pré-trust programmatique (spike S3) : écrit projects["<worktree>"].hasTrustDialogAccepted = true
|
|
// dans ~/.claude.json pour éviter le dialogue Trust au 1er lancement de `claude` dans un worktree neuf.
|
|
// Écriture ATOMIQUE (tmp + rename). Le chemin est injectable pour les tests (jamais le vrai HOME).
|
|
import { readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
import { homedir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
|
|
export function claudeConfigPath(): string {
|
|
return join(homedir(), '.claude.json');
|
|
}
|
|
|
|
/**
|
|
* Marque un worktree comme déjà approuvé. Tolérant : fichier absent → on le crée ; fichier corrompu
|
|
* → on ABANDONNE le pré-trust (on ne l'écrase pas, pour ne pas perdre la config existante). Retourne
|
|
* true si le pré-trust a été écrit, false sinon (l'appelant continue : ce n'est pas bloquant).
|
|
*/
|
|
export function preTrustProject(worktreePath: string, filePath = claudeConfigPath()): boolean {
|
|
let data: Record<string, unknown> = {};
|
|
try {
|
|
const parsed = JSON.parse(readFileSync(filePath, 'utf8')) as unknown;
|
|
if (typeof parsed !== 'object' || parsed === null) return false; // contenu inattendu : on n'écrase pas
|
|
data = parsed as Record<string, unknown>;
|
|
} catch (err) {
|
|
if ((err as NodeJS.ErrnoException).code !== 'ENOENT') return false; // corrompu/illisible : abandon
|
|
data = {}; // fichier absent : on part d'un objet vide
|
|
}
|
|
const projects =
|
|
typeof data.projects === 'object' && data.projects !== null
|
|
? (data.projects as Record<string, Record<string, unknown>>)
|
|
: {};
|
|
projects[worktreePath] = { ...(projects[worktreePath] ?? {}), hasTrustDialogAccepted: true };
|
|
data.projects = projects;
|
|
try {
|
|
const tmp = `${filePath}.arb-tmp`;
|
|
writeFileSync(tmp, JSON.stringify(data, null, 2));
|
|
renameSync(tmp, filePath); // rename atomique : jamais de fichier à moitié écrit
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|