feat(vscode): statut git détaillé, sessions archivées, lien IDE web, fetch/pull (0.2.0)

Reflète la refonte IDE en supervision légère, sans dupliquer l'éditeur :
- statut git fin dans l'arbre (staged/unstaged/conflits + dernier commit) ;
- commande « Open Worktree IDE » → deep-link /workspace/:repoId/:wt ;
- commandes fetch / pull (ff-only|rebase) sur les worktrees ;
- event WS session_archived + réglage showArchivedSessions (badge + filtre).
README/CHANGELOG + tests (config/rest-client/ws-client).
This commit is contained in:
2026-06-27 16:04:28 +02:00
parent 8a8fac75e6
commit d4e3ab47cd
13 changed files with 198 additions and 10 deletions
+21 -1
View File
@@ -21,7 +21,7 @@ export function worktreeTreeItem(worktree: WorktreeSummary): vscode.TreeItem {
item.contextValue = 'arboretum:worktree';
item.iconPath = new vscode.ThemeIcon(worktree.isMain ? 'star-full' : 'git-branch');
item.description = worktreeStatus(worktree);
item.tooltip = worktree.path;
item.tooltip = worktreeTooltip(worktree);
item.resourceUri = vscode.Uri.file(worktree.path);
return item;
}
@@ -32,9 +32,28 @@ function worktreeStatus(w: WorktreeSummary): string {
if (w.git.ahead > 0) parts.push(`↑${w.git.ahead}`);
if (w.git.behind > 0) parts.push(`↓${w.git.behind}`);
if (w.git.dirtyCount > 0) parts.push(`●${w.git.dirtyCount}`);
// Compteurs fins (P7, additifs) : staged/conflits surfacés directement dans l'arbre.
if (w.git.stagedCount && w.git.stagedCount > 0) parts.push(`+${w.git.stagedCount}`);
if (w.git.conflictCount && w.git.conflictCount > 0) parts.push(`!${w.git.conflictCount}`);
return parts.join(' ');
}
/** Tooltip détaillé : chemin + statut git fin (P7) + dernier commit. */
function worktreeTooltip(w: WorktreeSummary): vscode.MarkdownString {
const md = new vscode.MarkdownString();
md.appendMarkdown(`\`${w.path}\`\n\n`);
const g = w.git;
const status: string[] = [];
if (g.stagedCount && g.stagedCount > 0) status.push(`${g.stagedCount} staged`);
if (g.unstagedCount && g.unstagedCount > 0) status.push(`${g.unstagedCount} unstaged`);
if (g.conflictCount && g.conflictCount > 0) status.push(`${g.conflictCount} conflicts`);
if (status.length === 0 && g.dirtyCount > 0) status.push(`${g.dirtyCount} changed`);
md.appendMarkdown(status.length > 0 ? status.join(' · ') : 'clean');
if (g.ahead > 0 || g.behind > 0) md.appendMarkdown(` (↑${g.ahead} ↓${g.behind})`);
if (g.lastCommitSubject) md.appendMarkdown(`\n\n_${g.lastCommitSubject}_`);
return md;
}
export function sessionContextValue(s: SessionSummary): string {
let v = `arboretum:session:${s.live ? 'live' : 'dead'}`;
if (s.source === 'discovered') v += ':discovered';
@@ -72,6 +91,7 @@ function sessionDescription(s: SessionSummary): string {
// Affiche 'available' pour idle (cohérent avec le libellé web) ; l'enum reste 'idle' côté protocole.
if (s.live && s.activity) parts.push(s.activity === 'idle' ? 'available' : s.activity);
else if (!s.live) parts.push('exited');
if (s.archived) parts.push('archived');
if (s.source === 'discovered') parts.push('external');
if (s.groupId) parts.push('group');
if (s.waitingFor) parts.push(`— ${s.waitingFor}`);