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:
@@ -18,7 +18,7 @@ import type { FsEntry, FsListResponse } from '@arboretum/shared';
|
||||
*/
|
||||
export function registerFsRoutes(app: FastifyInstance): void {
|
||||
app.get('/api/v1/fs/list', async (req, reply) => {
|
||||
const q = req.query as { path?: string; markRepos?: string; showHidden?: string };
|
||||
const q = req.query as { path?: string; markRepos?: string; showHidden?: string; includeFiles?: string };
|
||||
const raw = q.path && q.path.length > 0 ? q.path : homedir();
|
||||
if (!raw.startsWith('/')) {
|
||||
return reply.status(400).send({ error: { code: 'BAD_PATH', message: 'path must be absolute' } });
|
||||
@@ -41,6 +41,7 @@ export function registerFsRoutes(app: FastifyInstance): void {
|
||||
|
||||
const markRepos = q.markRepos === '1' || q.markRepos === 'true';
|
||||
const showHidden = q.showHidden === '1' || q.showHidden === 'true';
|
||||
const includeFiles = q.includeFiles === '1' || q.includeFiles === 'true';
|
||||
|
||||
let dirents;
|
||||
try {
|
||||
@@ -63,13 +64,24 @@ export function registerFsRoutes(app: FastifyInstance): void {
|
||||
isDir = false;
|
||||
}
|
||||
}
|
||||
if (!isDir) continue;
|
||||
const full = join(abs, d.name);
|
||||
if (!isDir) {
|
||||
// En mode includeFiles, on remonte aussi les fichiers (arbre de l'IDE worktree).
|
||||
if (!includeFiles) continue;
|
||||
entries.push({ name: d.name, path: full, isFile: true });
|
||||
continue;
|
||||
}
|
||||
const entry: FsEntry = { name: d.name, path: full };
|
||||
if (markRepos && existsSync(join(full, '.git'))) entry.isRepo = true;
|
||||
entries.push(entry);
|
||||
}
|
||||
entries.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()));
|
||||
// Dossiers d'abord, puis fichiers ; tri insensible à la casse dans chaque groupe.
|
||||
entries.sort((a, b) => {
|
||||
const af = a.isFile ? 1 : 0;
|
||||
const bf = b.isFile ? 1 : 0;
|
||||
if (af !== bf) return af - bf;
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
});
|
||||
|
||||
const res: FsListResponse = {
|
||||
path: abs,
|
||||
|
||||
Reference in New Issue
Block a user