chore(typo): retire tous les tirets cadratins/demi-cadratins + garde CI
Some checks failed
CI / Build & test (Node 24) (push) Has been cancelled
CI / Pack & boot smoke (Node 22) (push) Has been cancelled
CI / No em/en dashes (push) Has been cancelled
CI / Build & test (Node 22) (push) Has been cancelled
Deploy site (production) / build-and-deploy (push) Successful in 20s

Remplace les 547 tirets cadratins (U+2014) et demi-cadratins (U+2013) des fichiers versionnés par la ponctuation contextuelle adaptée (point médian, deux-points, virgule, parenthèses ; tiret simple pour les plages), sur 122 fichiers (appli, vitrine, doc, tests, workflows, scripts).

Ajoute le job CI « lint-dashes » (git grep -P) qui échoue si un tiret cadratin/demi-cadratin réapparaît, hors logo binaire et captures brutes du terminal (fidélité des fixtures de détection de dialogue).
This commit is contained in:
2026-07-17 16:44:00 +02:00
parent 985531a986
commit 65ef616867
122 changed files with 538 additions and 521 deletions

View File

@@ -3,18 +3,18 @@
## 0.2.0
Follows the daemon's "worktree IDE" milestone (P7→P12), while keeping the extension a lightweight
**visual worktree manager** no editor/diff duplicated here, that lives in the web IDE:
**visual worktree manager**: no editor/diff duplicated here, that lives in the web IDE:
- **Detailed git status** in the worktree tree: staged / unstaged / conflict counts and the last commit
subject (from the daemon's enriched `WorktreeGitStatus`).
- **Open Worktree IDE** command deep-links to the web `/workspace/:repoId/:wt` view.
- **Open Worktree IDE** command: deep-links to the web `/workspace/:repoId/:wt` view.
- **Fetch** and **Pull** (fast-forward only or rebase) commands on worktrees, alongside commit/push.
- **Archived sessions**: the `session_archived` event is handled live; finished, auto-archived sessions
are badged and hidden by default, with a new `arboretum.showArchivedSessions` setting to reveal them.
## 0.1.0
Initial release native VS Code integration for the Arboretum daemon:
Initial release: native VS Code integration for the Arboretum daemon:
- Real-time **Repositories** and **Groups** trees (repos → worktrees → sessions) over the daemon WebSocket.
- **Native terminals** (`Pseudoterminal`) to attach/observe any session, reusing VS Code's rendering and

View File

@@ -1,26 +1,26 @@
# Arboretum for VS Code
Pilot your git **worktrees** and **Claude Code sessions** from inside VS Code a real native
Pilot your git **worktrees** and **Claude Code sessions** from inside VS Code: a real native
integration on top of the [Arboretum](https://git.lidge.fr/johanleroy/arboretum) daemon, not a webview.
- **Live tree** of Repositories → Worktrees → Sessions (and a Groups view), updated in real time over
the daemon's WebSocket.
- **Native terminals**: attach to any session in a real VS Code terminal (a `Pseudoterminal` bridges the
daemon's PTY) you get VS Code's own rendering, scrollback, copy/paste and links for free.
daemon's PTY): you get VS Code's own rendering, scrollback, copy/paste and links for free.
- **Waiting alerts**: a status-bar counter and native notifications when a Claude session is waiting for
input answer **Yes/No** without even opening the terminal (uses the daemon's `answer` command).
- **Git mutations** from the tree: create worktree, commit, push, fetch, pull, promote to main with a
input: answer **Yes/No** without even opening the terminal (uses the daemon's `answer` command).
- **Git mutations** from the tree: create worktree, commit, push, fetch, pull, promote to main, with a
**detailed git status** on each worktree (staged / unstaged / conflicts and the last commit subject).
- **Open Worktree IDE**: jump from any worktree to its full `/workspace` view (file tree, Monaco editor,
inline diffs, integrated terminal) in the browser. The extension stays a lightweight visual worktree
manager the heavy editing lives in the web IDE.
manager: the heavy editing lives in the web IDE.
- **Workspace-aware**: the worktree matching your open folder is highlighted; start a session or create a
worktree for the current folder in one command.
## Requirements
- A running Arboretum daemon (`npx @johanleroy/git-arboretum`, or installed as a user service via
`arboretum install`). The extension is a **client** it does not start the daemon.
`arboretum install`). The extension is a **client**: it does not start the daemon.
- An access token. The bootstrap token is printed once on first daemon start; you can also create one in
the Arboretum dashboard (**Settings → Tokens**).
@@ -41,7 +41,7 @@ integration on top of the [Arboretum](https://git.lidge.fr/johanleroy/arboretum)
| `arboretum.notifyOnWaiting` | `true` | Native notification when a session starts waiting for input. |
The extension authenticates with `Authorization: Bearer <token>` on both REST and the WebSocket upgrade.
For remote access, point `arboretum.url` at your Tailscale Serve URL (`https://…`) the WebSocket is
For remote access, point `arboretum.url` at your Tailscale Serve URL (`https://…`): the WebSocket is
derived automatically (`wss://`).
## Building & packaging (private VSIX)

View File

@@ -1,7 +1,7 @@
{
"name": "git-arboretum",
"displayName": "Arboretum",
"description": "Pilot your git worktrees and Claude Code sessions from VS Code native terminals, live tree, waiting alerts.",
"description": "Pilot your git worktrees and Claude Code sessions from VS Code: native terminals, live tree, waiting alerts.",
"version": "0.2.0",
"private": true,
"publisher": "johanleroy",

View File

@@ -1,4 +1,4 @@
// Client REST typé du daemon Arboretum module pur (pas d'import vscode), testable.
// Client REST typé du daemon Arboretum : module pur (pas d'import vscode), testable.
// Auth par en-tête `Authorization: Bearer <token>` (le hook preValidation du serveur l'accepte
// au même titre que le cookie). Un client Node n'envoie pas d'en-tête Origin → il passe le check
// Origin strict côté serveur. Réutilise les types de `@arboretum/shared`.

View File

@@ -1,4 +1,4 @@
// Client WebSocket multiplexé Node portage du client web (packages/web/src/lib/ws-client.ts).
// Client WebSocket multiplexé Node : portage du client web (packages/web/src/lib/ws-client.ts).
// UNE connexion, plusieurs canaux ; handshake hello/hello_ok, sub des topics, corrélation FIFO des
// 'attached', flow control par ACK (cumul d'octets PAR CANAL, remis à 0 à chaque RESYNC), reconnexion
// avec backoff. Différences vs web :
@@ -39,7 +39,7 @@ export interface AttachOptions {
sink: AttachmentSink;
}
/** Transport minimal abstrait pour les tests ; l'impl par défaut enveloppe `ws`. */
/** Transport minimal : abstrait pour les tests ; l'impl par défaut enveloppe `ws`. */
export interface RawSocket {
send(data: string): void;
close(): void;
@@ -404,7 +404,7 @@ export class ArbWsClient {
return;
case 'error': {
if (msg.channel !== undefined) {
this.opts.log?.(`channel ${msg.channel}: ${msg.code} ${msg.message}`);
this.opts.log?.(`channel ${msg.channel}: ${msg.code}: ${msg.message}`);
return;
}
if ((msg.code === 'NOT_FOUND' || msg.code === 'SESSION_EXITED') && this.awaitingAttached.length > 0) {
@@ -420,7 +420,7 @@ export class ArbWsClient {
}
return;
}
this.opts.log?.(`${msg.code} ${msg.message}`);
this.opts.log?.(`${msg.code}: ${msg.message}`);
return;
}
case 'pong':

View File

@@ -42,7 +42,7 @@ async function run(label: string, fn: () => Promise<void>): Promise<void> {
await fn();
} catch (err) {
const msg = err instanceof RestError ? `${err.code}: ${err.message}` : (err as Error).message;
void vscode.window.showErrorMessage(`Arboretum ${label}: ${msg}`);
void vscode.window.showErrorMessage(`Arboretum · ${label}: ${msg}`);
}
}
@@ -310,7 +310,7 @@ async function promoteWorktreeFlow(deps: CommandDeps, repoId: string, worktree:
return;
}
const msg = err instanceof RestError ? `${err.code}: ${err.message}` : (err as Error).message;
void vscode.window.showErrorMessage(`Arboretum promote: ${msg}`);
void vscode.window.showErrorMessage(`Arboretum · promote: ${msg}`);
}
}
@@ -326,7 +326,7 @@ async function startGroupSessionFlow(deps: CommandDeps, group: GroupSummary): Pr
deps.terminals.open(res.session, 'interactive');
if (res.skipped.length > 0) {
void vscode.window.showWarningMessage(
`Arboretum: ${res.skipped.length} repo(s) skipped ${res.skipped.map((s) => s.reason).join('; ')}`,
`Arboretum: ${res.skipped.length} repo(s) skipped: ${res.skipped.map((s) => s.reason).join('; ')}`,
);
}
});

View File

@@ -1,4 +1,4 @@
// Lecture des réglages `arboretum.*` module pur (aucun import vscode) pour rester testable.
// Lecture des réglages `arboretum.*` : module pur (aucun import vscode) pour rester testable.
// L'extension fournit un `ConfigSnapshot` lu depuis workspace.getConfiguration ; ce module
// normalise/dérive les valeurs (notamment l'URL WebSocket à partir de l'URL HTTP).
@@ -22,7 +22,7 @@ export function wsUrlFromBase(baseUrl: string): string {
}
/**
* Construit l'URL de la vue IDE web d'un worktree deep-link vers la route `/workspace/:repoId/:wt`
* Construit l'URL de la vue IDE web d'un worktree : deep-link vers la route `/workspace/:repoId/:wt`
* (le routeur web est en history mode). `repoId` et le chemin du worktree sont encodés par segment.
*/
export function workspaceUrl(baseUrl: string, repoId: string, worktreePath: string): string {

View File

@@ -93,7 +93,7 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
try {
await probe.me();
} catch (err) {
void vscode.window.showErrorMessage(`Arboretum: sign in failed ${(err as Error).message}`);
void vscode.window.showErrorMessage(`Arboretum: sign in failed: ${(err as Error).message}`);
return;
}
await auth.storeToken(token);

View File

@@ -41,7 +41,7 @@ export class WaitingNotifier implements vscode.Disposable {
private notify(session: SessionSummary): void {
const title = session.title?.trim() || session.command;
const detail = session.waitingFor ? ` ${session.waitingFor}` : '';
const detail = session.waitingFor ? ` · ${session.waitingFor}` : '';
void vscode.window
.showInformationMessage(`Arboretum: "${title}" is waiting for input${detail}`, 'Open Terminal', 'Yes', 'No')
.then((choice) => {

View File

@@ -1,4 +1,4 @@
// État mémoire de l'extension module pur (aucun import vscode), testable.
// État mémoire de l'extension : module pur (aucun import vscode), testable.
// Seedé via REST au (re)connect, puis patché par les events WS. Émet onDidChange à chaque mutation ;
// les arbres / status bar / notifications s'y abonnent. Principe « groupe léger » : on ne corrèle pas
// les worktrees aux sessions via la liste embarquée (potentiellement périmée) mais par `cwd`, exactement
@@ -194,7 +194,7 @@ export class Store {
.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
}
/** sessions en attente d'input (activity === 'waiting') pour la status bar et les notifications. */
/** sessions en attente d'input (activity === 'waiting') : pour la status bar et les notifications. */
waitingSessions(): SessionSummary[] {
return [...this.sessions.values()].filter((s) => s.live && s.activity === 'waiting');
}

View File

@@ -1,4 +1,4 @@
// Pont d'une session vers un terminal module pur (aucun import vscode), testable.
// Pont d'une session vers un terminal : module pur (aucun import vscode), testable.
// Le ws-client remet des octets BRUTS (un chunk PTY peut couper un caractère UTF-8 en frontière de
// frame, exactement comme côté web où xterm.write(Uint8Array) gérait le décodage). Ici la cible est
// un Pseudoterminal qui veut des `string` → on décode en streaming avec un TextDecoder, et on recrée

View File

@@ -1,5 +1,5 @@
// Adaptateur vscode.Pseudoterminal : expose une session Arboretum comme un terminal NATIF de VS Code
// (rendu, scrollback, copier/coller, liens gratuits). C'est la pièce maîtresse de l'intégration
// (rendu, scrollback, copier/coller, liens gratuits). C'est la pièce maîtresse de l'intégration :
// pas de xterm-in-webview. Le pont WS/décodage vit dans SessionBridge (pur) ; cet adaptateur ne fait
// que relier les events Pseudoterminal ↔ bridge.
import * as vscode from 'vscode';
@@ -45,13 +45,13 @@ export class SessionTerminalManager {
},
onControlChanged: (controlling) => {
if (!controlling && mode === 'interactive') {
writeEmitter.fire('\r\n\x1b[33m[arboretum: control lost now observing]\x1b[0m\r\n');
writeEmitter.fire('\r\n\x1b[33m[arboretum: control lost: now observing]\x1b[0m\r\n');
}
},
});
this.bridges.set(session.id, bridge);
bridge.open(dims?.columns ?? 80, dims?.rows ?? 24).catch((err: Error) => {
writeEmitter.fire(`\r\n\x1b[31m[arboretum: cannot attach ${err.message}]\x1b[0m\r\n`);
writeEmitter.fire(`\r\n\x1b[31m[arboretum: cannot attach: ${err.message}]\x1b[0m\r\n`);
closeEmitter.fire();
});
},

View File

@@ -94,12 +94,12 @@ function sessionDescription(s: SessionSummary): string {
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}`);
if (s.waitingFor) parts.push(`· ${s.waitingFor}`);
return parts.join(' ');
}
function sessionTooltip(s: SessionSummary): string {
const lines = [`${s.command} ${s.cwd}`, `status: ${s.status}${s.live ? ' (live)' : ''}`];
const lines = [`${s.command} · ${s.cwd}`, `status: ${s.status}${s.live ? ' (live)' : ''}`];
if (s.activity) lines.push(`activity: ${s.activity}`);
if (s.addedDirs && s.addedDirs.length > 0) lines.push(`added dirs: ${s.addedDirs.length}`);
return lines.join('\n');