feat(p7): moteur git détaillé, API fichiers et watcher FS temps réel

Fondations de la refonte « vrai IDE IA worktree » (chantier P7→P12).

- git.ts : listChanges (status --porcelain=v2 -z + --numstat fusionnés),
  fileDiff (diff unifié borné, refus binaire, untracked via --no-index),
  stage/unstage/restore/clean, commitStaged, amendCommit (refus si poussé),
  fetchRemote, pull (ff-only|rebase), lastCommit, resolveGitDir,
  isSafeRelativePath ; worktreeStatus enrichi (compteurs staged/unstaged/
  conflict + dernier commit), champs additifs WorktreeGitStatus.
- core/fs-watcher.ts : FsWatcherService (chokidar, refcount + pool LRU borné,
  ignore .git sauf HEAD/index, débounce) → invalide factsCache, rediffuse
  worktree_update et émet worktree_changes.
- WorktreeManager : getWorktreeChanges/getFileDiff/stage/unstage/discard/
  fetch/pull/watch/unwatch/assertPathInWorktree ; commitWorktree { mode, amend }.
- routes/git.ts (changes/diff/stage/unstage/discard/fetch/pull) et
  routes/files.ts (GET/PUT contenu fichier, bornage strict au worktree) ;
  fs/list?includeFiles=1 (flag isFile).
- protocole ADDITIF (PROTOCOL_VERSION inchangé) : messages WS watch/unwatch
  (validés dans parseClientMessage) + worktree_changes poussé ciblé par la
  gateway ; front wsClient.watchWorktree + lib/git-api.ts + api.put.
- tests git/fs-watcher/protocole + acceptance-p7.mjs (ALL GREEN).
- dépendance : chokidar (serveur).
This commit is contained in:
2026-06-27 12:15:00 +02:00
parent 062bb64d41
commit be43911dc0
21 changed files with 1610 additions and 21 deletions

View File

@@ -134,12 +134,18 @@ export function registerWorktreeRoutes(app: FastifyInstance, wt: WorktreeManager
if (!body || typeof body.path !== 'string') {
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'path is required' } });
}
if (typeof body.message !== 'string' || body.message.trim() === '') {
const amend = body.amend === true;
if (!amend && (typeof body.message !== 'string' || body.message.trim() === '')) {
return reply.status(400).send({ error: { code: 'BAD_REQUEST', message: 'message is required' } });
}
const mode = body.mode === 'staged' ? 'staged' : 'all';
try {
const worktree = await wt.commitWorktree(id, body.path, body.message.trim());
recordAudit(db, { actor: req.authContext?.tokenId ?? 'unknown', action: 'worktree.commit', resourceId: id, details: { path: body.path } });
const worktree = await wt.commitWorktree(id, body.path, {
message: typeof body.message === 'string' ? body.message.trim() : '',
mode,
amend,
});
recordAudit(db, { actor: req.authContext?.tokenId ?? 'unknown', action: 'worktree.commit', resourceId: id, details: { path: body.path, mode, amend } });
return reply.send({ worktree } satisfies WorktreeResponse);
} catch (err) {
return sendManagerError(reply, err);