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
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:
@@ -77,7 +77,7 @@ afterAll(async () => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe('app e2e — auth, origin et sessions', () => {
|
||||
describe('app e2e · auth, origin et sessions', () => {
|
||||
let t: TestApp;
|
||||
let cookieValue: string;
|
||||
|
||||
@@ -277,7 +277,7 @@ describe('app e2e — auth, origin et sessions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('app e2e — rate limit du login', () => {
|
||||
describe('app e2e · rate limit du login', () => {
|
||||
it('mauvais token et bodies invalides → 401, puis 429 dès le 4e essai (backoff après 3 échecs consécutifs)', async () => {
|
||||
const t = makeApp('ratelimit-failures');
|
||||
const bad = await t.bundle.app.inject({ method: 'POST', url: '/api/v1/auth/login', payload: { token: 'arb_badbadbadbad' } });
|
||||
@@ -309,7 +309,7 @@ describe('app e2e — rate limit du login', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('app e2e — découverte, resume & fork (P2)', () => {
|
||||
describe('app e2e · découverte, resume & fork (P2)', () => {
|
||||
let t: TestApp;
|
||||
let projectsDir: string;
|
||||
let sessionsDir: string;
|
||||
@@ -370,7 +370,7 @@ describe('app e2e — découverte, resume & fork (P2)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('app e2e — découverte auto des repos & masquage', () => {
|
||||
describe('app e2e · découverte auto des repos & masquage', () => {
|
||||
let t: TestApp;
|
||||
let scanRoot: string;
|
||||
const bearer = (): Record<string, string> => ({ authorization: `Bearer ${t.token}` });
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Lot 5 — sécurité enterprise : en-têtes de sécurité, journal d'audit, RGPD (export/suppression),
|
||||
// Lot 5 · sécurité enterprise : en-têtes de sécurité, journal d'audit, RGPD (export/suppression),
|
||||
// garde Content-Type. Vérifie le câblage de bout en bout via buildApp + token bootstrap.
|
||||
import { mkdtempSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
|
||||
@@ -4,7 +4,7 @@ import { buildSpawnSpec, diagnoseClaudeBin, resolveClaudeBin } from '../src/core
|
||||
// resolveClaudeBin() fait `which claude` : on le stub pour ne pas dépendre d'un claude réel en PATH.
|
||||
vi.mock('node:child_process', () => ({ execFileSync: () => '/usr/bin/claude\n' }));
|
||||
|
||||
describe('buildSpawnSpec — session de groupe multi-repo (P6)', () => {
|
||||
describe('buildSpawnSpec · session de groupe multi-repo (P6)', () => {
|
||||
it('émet un --add-dir par répertoire supplémentaire (claude)', () => {
|
||||
const spec = buildSpawnSpec({ command: 'claude', addDirs: ['/a', '/b', '/c'] });
|
||||
expect(spec.file).toBe('/usr/bin/claude');
|
||||
@@ -28,7 +28,7 @@ describe('buildSpawnSpec — session de groupe multi-repo (P6)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveClaudeBin / diagnoseClaudeBin — override de chemin (réglage UI)', () => {
|
||||
describe('resolveClaudeBin / diagnoseClaudeBin · override de chemin (réglage UI)', () => {
|
||||
// process.execPath (node) est un fichier exécutable réel garanti sur la machine de test.
|
||||
const realBin = process.execPath;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
launchAgentPlistPath,
|
||||
} from '../src/cli/install.js';
|
||||
|
||||
describe('cli install — detectPlatform', () => {
|
||||
describe('cli install · detectPlatform', () => {
|
||||
it('accepte linux et darwin', () => {
|
||||
expect(detectPlatform('linux')).toBe('linux');
|
||||
expect(detectPlatform('darwin')).toBe('darwin');
|
||||
@@ -26,7 +26,7 @@ describe('cli install — detectPlatform', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — buildServiceArgs', () => {
|
||||
describe('cli install · buildServiceArgs', () => {
|
||||
it('aucun flag → aucun argument (le service garde les défauts loopback)', () => {
|
||||
expect(buildServiceArgs(parseInstallArgs([]))).toEqual([]);
|
||||
});
|
||||
@@ -47,7 +47,7 @@ describe('cli install — buildServiceArgs', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — resolveBin', () => {
|
||||
describe('cli install · resolveBin', () => {
|
||||
it('par défaut : node (process.execPath) + dist/index.js', () => {
|
||||
const { exec, args } = resolveBin({});
|
||||
expect(exec).toBe(process.execPath);
|
||||
@@ -64,7 +64,7 @@ describe('cli install — resolveBin', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — renderSystemdUnit', () => {
|
||||
describe('cli install · renderSystemdUnit', () => {
|
||||
it('contient le ExecStart calculé et les directives clés', () => {
|
||||
const unit = renderSystemdUnit({ exec: '/usr/bin/node', scriptArgs: ['/opt/arboretum/index.js', '--port', '7317'] });
|
||||
expect(unit).toContain('ExecStart=/usr/bin/node /opt/arboretum/index.js --port 7317');
|
||||
@@ -101,7 +101,7 @@ describe('cli install — renderSystemdUnit', () => {
|
||||
});
|
||||
expect(unit).toMatchInlineSnapshot(`
|
||||
"[Unit]
|
||||
Description=Arboretum — git worktree & Claude Code dashboard
|
||||
Description=Arboretum · git worktree & Claude Code dashboard
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
@@ -120,7 +120,7 @@ describe('cli install — renderSystemdUnit', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — renderLaunchAgentPlist', () => {
|
||||
describe('cli install · renderLaunchAgentPlist', () => {
|
||||
const base = {
|
||||
label: 'fr.lidge.arboretum',
|
||||
programArguments: ['/usr/bin/node', '/opt/index.js', '--port', '7317'],
|
||||
@@ -163,13 +163,13 @@ describe('cli install — renderLaunchAgentPlist', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — xmlEscape', () => {
|
||||
describe('cli install · xmlEscape', () => {
|
||||
it('échappe &, < et >', () => {
|
||||
expect(xmlEscape('a & b < c > d')).toBe('a & b < c > d');
|
||||
});
|
||||
});
|
||||
|
||||
describe('cli install — chemins', () => {
|
||||
describe('cli install · chemins', () => {
|
||||
const savedXdg = process.env.XDG_CONFIG_HOME;
|
||||
afterEach(() => {
|
||||
if (savedXdg === undefined) delete process.env.XDG_CONFIG_HOME;
|
||||
|
||||
@@ -24,7 +24,7 @@ async function replay(fixture: string): Promise<Array<{ dialog: ClassifiedDialog
|
||||
return seen;
|
||||
}
|
||||
|
||||
describe('ScreenReader (@xterm/headless) — anti strip-ANSI naïf', () => {
|
||||
describe('ScreenReader (@xterm/headless) · anti strip-ANSI naïf', () => {
|
||||
it('préserve les espaces du dialogue (« Do you want to create », pas « Doyouwant »)', async () => {
|
||||
const seen = await replay('perm-write2.raw');
|
||||
expect(seen.length).toBeGreaterThan(0);
|
||||
@@ -32,7 +32,7 @@ describe('ScreenReader (@xterm/headless) — anti strip-ANSI naïf', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('classifyDialog — unitaire (lignes synthétiques)', () => {
|
||||
describe('classifyDialog · unitaire (lignes synthétiques)', () => {
|
||||
it('trust', () => {
|
||||
const d = classifyDialog(['Do you trust the files in this folder?', '❯ 1. Yes, I trust this folder', '2. No, exit']);
|
||||
expect(d?.kind).toBe('trust');
|
||||
@@ -81,7 +81,7 @@ describe('détection sur fixtures réelles S3 (replay headless)', () => {
|
||||
|
||||
// Campagne de fiabilité P4-C : chaque type de dialogue ciblé par la supervision mobile doit être
|
||||
// classifié de façon fiable (cf. « reste à faire P4 » du verdict S3 : couvrir le refus Esc et le plan).
|
||||
describe('campagne de fiabilité P4-C — tous les types de dialogue', () => {
|
||||
describe('campagne de fiabilité P4-C · tous les types de dialogue', () => {
|
||||
const realCaptures: Array<{ fixture: string; kind: DialogKind }> = [
|
||||
{ fixture: 'trust.raw', kind: 'trust' },
|
||||
{ fixture: 'perm-write2.raw', kind: 'permission' },
|
||||
@@ -101,7 +101,7 @@ describe('campagne de fiabilité P4-C — tous les types de dialogue', () => {
|
||||
expect(seen.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('plan (synthétique — pas de capture réelle) : « Would you like to proceed? »', () => {
|
||||
it('plan (synthétique, pas de capture réelle) : « Would you like to proceed? »', () => {
|
||||
const d = classifyDialog([
|
||||
'Here is my implementation plan:',
|
||||
' - step one',
|
||||
|
||||
@@ -184,7 +184,7 @@ describe('branche : existence, liste, courante, commit', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('P7 — parseurs purs (status v2 / numstat)', () => {
|
||||
describe('P7 · parseurs purs (status v2 / numstat)', () => {
|
||||
it('parsePorcelainV2 : modifié, untracked, renommé', () => {
|
||||
const z =
|
||||
'1 .M N... 100644 100644 100644 h1 h2 file.txt\x00' +
|
||||
@@ -217,7 +217,7 @@ describe('P7 — parseurs purs (status v2 / numstat)', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('P7 — changes / diff / staging / commit sélectif (repo tmp)', () => {
|
||||
describe('P7 · changes / diff / staging / commit sélectif (repo tmp)', () => {
|
||||
it('listChanges : untracked + modifié non indexé + indexé', async () => {
|
||||
const repo = makeTmpRepo();
|
||||
writeFileSync(join(repo, 'untracked.txt'), 'x\n');
|
||||
|
||||
@@ -463,7 +463,7 @@ describe('PtyManager (pty mocké)', () => {
|
||||
manager.attach(summary.id, b, 80, 24);
|
||||
|
||||
const chunk = 'x'.repeat(200 * 1024); // 200 Kio ASCII
|
||||
pty.emitData(chunk); // a=200K, b=200K — sous HIGH (384K)
|
||||
pty.emitData(chunk); // a=200K, b=200K : sous HIGH (384K)
|
||||
expect(pty.pause).not.toHaveBeenCalled();
|
||||
|
||||
manager.ack(summary.id, b, 200 * 1024); // b rattrape tout
|
||||
@@ -516,7 +516,7 @@ describe('PtyManager (pty mocké)', () => {
|
||||
const chunks = ['A', 'B', 'C', 'D'].map((c) => c.repeat(mib));
|
||||
|
||||
pty.emitData(chunks[0]!); // outstanding 1 Mio
|
||||
pty.emitData(chunks[1]!); // outstanding 2 Mio — pas strictement > LAGGING_BYTES
|
||||
pty.emitData(chunks[1]!); // outstanding 2 Mio : pas strictement > LAGGING_BYTES
|
||||
expect(a.lagging).toBe(false);
|
||||
expect(a.sendOutput).toHaveBeenCalledTimes(2);
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ function mulberry32(seed: number): () => number {
|
||||
|
||||
const buf = (s: string): Buffer => Buffer.from(s, 'ascii');
|
||||
|
||||
describe('RingBuffer — écriture simple', () => {
|
||||
describe('RingBuffer · écriture simple', () => {
|
||||
it('écrit puis relit sans wrap', () => {
|
||||
const ring = new RingBuffer(16);
|
||||
ring.write(buf('hello'));
|
||||
@@ -76,7 +76,7 @@ describe('RingBuffer — écriture simple', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RingBuffer — wrap-around', () => {
|
||||
describe('RingBuffer · wrap-around', () => {
|
||||
it('wrap simple : le chunk chevauche la frontière du buffer', () => {
|
||||
const ring = new RingBuffer(8);
|
||||
ring.write(buf('01234')); // pos 0..5
|
||||
@@ -120,7 +120,7 @@ describe('RingBuffer — wrap-around', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RingBuffer — chunk >= capacité', () => {
|
||||
describe('RingBuffer · chunk >= capacité', () => {
|
||||
it('chunk == capacité sur un ring vierge', () => {
|
||||
const ring = new RingBuffer(8);
|
||||
ring.write(buf('abcdefgh'));
|
||||
@@ -156,7 +156,7 @@ describe('RingBuffer — chunk >= capacité', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RingBuffer — readFrom', () => {
|
||||
describe('RingBuffer · readFrom', () => {
|
||||
function filled(): RingBuffer {
|
||||
// total = 10, capacité 8 → fenêtre = offsets [2, 10)
|
||||
const ring = new RingBuffer(8);
|
||||
@@ -192,7 +192,7 @@ describe('RingBuffer — readFrom', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RingBuffer — totalOffset monotone', () => {
|
||||
describe('RingBuffer · totalOffset monotone', () => {
|
||||
it('croît exactement de la taille de chaque chunk, jamais ne décroît', () => {
|
||||
const ring = new RingBuffer(8);
|
||||
const rand = mulberry32(0x5eed);
|
||||
@@ -207,7 +207,7 @@ describe('RingBuffer — totalOffset monotone', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('RingBuffer — équivalence avec le modèle de référence (property test)', () => {
|
||||
describe('RingBuffer · équivalence avec le modèle de référence (property test)', () => {
|
||||
it('tail et readFrom identiques au modèle pour 500 écritures aléatoires (chunks < capacité)', () => {
|
||||
const capacity = 64;
|
||||
const ring = new RingBuffer(capacity);
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('GET /api/v1/settings', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — sécurité', () => {
|
||||
describe('PATCH /api/v1/settings · sécurité', () => {
|
||||
it('ignore toute clé hors allow-list (ne touche pas aux secrets)', async () => {
|
||||
const before = getSetting(db, 'server_secret');
|
||||
await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { server_secret: 'pwned', vapid_private: 'pwned' } });
|
||||
@@ -115,7 +115,7 @@ describe('PATCH /api/v1/settings — sécurité', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — découverte des dépôts', () => {
|
||||
describe('PATCH /api/v1/settings · découverte des dépôts', () => {
|
||||
it('enregistre des scanRoots et un intervalle valides et les renvoie', async () => {
|
||||
const res = await bundle.app.inject({
|
||||
method: 'PATCH',
|
||||
@@ -148,7 +148,7 @@ describe('PATCH /api/v1/settings — découverte des dépôts', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — Claude CLI', () => {
|
||||
describe('PATCH /api/v1/settings · Claude CLI', () => {
|
||||
// process.execPath (node) est un fichier exécutable réel garanti sur la machine de test.
|
||||
const realBin = process.execPath;
|
||||
|
||||
@@ -191,7 +191,7 @@ describe('PATCH /api/v1/settings — Claude CLI', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('PATCH /api/v1/settings — rétention des sessions (P10)', () => {
|
||||
describe('PATCH /api/v1/settings · rétention des sessions (P10)', () => {
|
||||
it('enregistre une rétention valide et l’expose', async () => {
|
||||
const res = await bundle.app.inject({ method: 'PATCH', url: '/api/v1/settings', headers: auth(), payload: { retentionDays: 7 } });
|
||||
expect(res.statusCode).toBe(200);
|
||||
|
||||
Reference in New Issue
Block a user