Projets et workspaces — modèle et API
Audience : développeur qui travaille sur le CRUD projets/workspaces ou intègre l’arborescence .arka-deck/.
Workspace (groupe — uniquement métadonnées) │ ├── Project A (= dossier disque + marker .arka-deck/project/marker.json) ├── Project B └── Project C- Workspace : pure métadonnée. Persisté dans l’index global utilisateur.
- Project : un dossier disque sur la machine. Le marker disque (
<projectPath>/.arka-deck/project/marker.json) est la source de vérité. - Index global :
<arkaHome>/index/projects.json+workspaces.json= vue agrégée reconstructible.
Path par défaut : ~/.arka-deck/ (override via env ARKA_DECK_HOME).
1. Modèles
Section intitulée « 1. Modèles »Workspace
Section intitulée « Workspace »interface Workspace { id: string; // UUID v4 name: string; // unique dans l'index, ≤ 80 chars description: string; createdAt: string; // ISO 8601}Persisté uniquement dans <arkaHome>/index/workspaces.json.
interface Project { id: string; // UUID v4 name: string; // libellé affichable, ≤ 80 chars description: string; path: string; // chemin absolu du dossier projet, IMMUABLE workspaceId: string; createdAt: string; lastUsedAt: string; // mis à jour à chaque ouverture}Persisté deux fois :
- Marker disque :
<projectPath>/.arka-deck/project/marker.json← source de vérité - Index global :
<arkaHome>/index/projects.json← reconstructible depuis les markers
En cas de conflit (le marker dit X, l’index dit Y) → le marker gagne (cf. findByCwd qui réimporte automatiquement l’index si désynchronisé).
2. Architecture (hexagonale)
Section intitulée « 2. Architecture (hexagonale) »Ports outbound
Section intitulée « Ports outbound »| Port | Rôle |
|---|---|
ProjectStore | Lit/écrit le marker disque |
ProjectIndexStore | Lit/écrit l’index global utilisateur |
WorkspaceStore | Lit/écrit l’index global workspaces |
InstalledItemsStore | Lit la liste des items installés (agents/hooks/skills) — utilisé par purge |
ChatSessionStore | Lit/supprime les sessions chat liées (utilisé par forget/purge) |
Filesystem | Wrapper FS (read/write/exists/rm/readdir/…) |
Ports inbound
Section intitulée « Ports inbound »ForProjects :
create({name, path, description, workspaceId})— crée marker + indexlist()— tous les projets, ordrelastUsedAtdesclistByWorkspace(workspaceId)— filtregetById(id)— lookup indexforget(id)— retire de l’index, marker disque préservéupdate({id, name, description, workspaceId})— patch (pathimmuable)touchLastUsed(id)— met à jourlastUsedAtfindByCwd(cwd)— détection auto au bootpurge(id)— forget + cleanup.arka-deck/+ agents installés + sessions chat
ForWorkspaces :
create({name, description})— name doit être uniquelist()— tous, du plus récent au plus anciengetById(id)update({id, name, description})— re-vérifie unicitédelete(id)— refuse si projets rattachés (WorkspaceNotEmptyError)
Adapters prod
Section intitulée « Adapters prod »| Adapter | Fichier |
|---|---|
FsProjectStore | adapters/outbound/filesystem/fs-project-store.ts |
FsProjectIndexStore | adapters/outbound/filesystem/fs-project-index-store.ts |
FsWorkspaceStore | adapters/outbound/filesystem/fs-workspace-store.ts |
3. Arborescence disque
Section intitulée « 3. Arborescence disque »~/.arka-deck/ ← arkaHome (override par env ARKA_DECK_HOME) index/ workspaces.json ← array de Workspace projects.json ← array de Project (vue index) preferences/ user.json ← prefs user (langue, etc.) security.json ← allowlist paths autorisés cache/ catalogue/... ← cache HYOS debrief/... ← cache atoms HCM mémoire providers/ providers.db ← SQLite instances providers secrets/ secrets.key ← clé maître AES-256-GCM (mode 600) chat/ chat.db ← SQLite sessions/messages chat
<projectPath>/ ← n'importe où sur disque .arka-deck/ project/ marker.json ← source de vérité projet agents/installed.json hooks/installed.json skills/installed.json memory/ ← cf. addons-firstparty/memory-local/ .claude/ agents/<slug>/ ← agent recruté (profil HYOS) settings.json ← settings Claude Code (jamais touché par arka-deck)4. Cycle de vie d’un projet
Section intitulée « 4. Cycle de vie d’un projet »Création
Section intitulée « Création »ForProjects.create({ name, path, description, workspaceId }) ↓[Vérifie path = directory disque] ↓[Vérifie marker absent (anti-doublon)] ↓[Vérifie workspaceId existe] ↓[Filesystem.mkdir <path>/.arka-deck/project/, recursive] ↓[ProjectStore.save → marker.json] ↓[ProjectIndexStore.save → ajout à projects.json] ↓EventBus → 'project.created' (async)Détection au boot — findByCwd
Section intitulée « Détection au boot — findByCwd »ForProjects.findByCwd(cwd) ↓[ProjectStore.exists(cwd) ? = lookup marker] ↓ ├─ Marker absent → null (pas un projet arka-deck) │ └─ Marker présent ↓ [Lit marker → Project] ↓ [ProjectIndexStore.getById → présent ?] ↓ ├─ Oui → renvoie projet └─ Non → réimporte (l'index est reconstructible) + renvoie projetCas typique : un utilisateur fait git clone d’un projet existant sur une autre machine. arka-deck détecte le marker au prochain boot et l’ajoute à son index.
Forget vs Purge
Section intitulée « Forget vs Purge »| Mode | Index | Marker disque | .arka-deck/ | Agents .claude/agents/ | Sessions chat SQLite |
|---|---|---|---|---|---|
forget | retiré | préservé | préservé | préservés | supprimées |
purge | retiré | supprimé | supprimé | supprimés (uniquement les arka-deck installés) | supprimées |
purge ne touche jamais aux fichiers .claude/ non-arka-deck (settings user, agents non-arka). Seulement aux artefacts dont arka-deck a la trace dans installed.json.
Code : core/use-cases/projects/build-for-projects.ts:purgeProjectUseCase.
Allowlist sécurité
Section intitulée « Allowlist sécurité »Le serveur web vérifie qu’un path de création projet est dans l’allowlist utilisateur avant d’écrire le marker. Allowlist par défaut : ~, ~/Documents, ~/Projects, etc. — configurable dans ~/.arka-deck/preferences/security.json.
Refus → HTTP 403 PATH_NOT_ALLOWED avec body listant les emplacements autorisés.
5. Cycle de vie d’un workspace
Section intitulée « 5. Cycle de vie d’un workspace »Création
Section intitulée « Création »forWorkspaces.create({ name: 'Arkalabs', description: 'Workspace principal' });namedoit être unique global (pas par utilisateur — single-user)- Levée
WorkspaceNameAlreadyExistsErrorsinon
Suppression — invariant non-vide
Section intitulée « Suppression — invariant non-vide »await forWorkspaces.delete(workspaceId);// → throw WorkspaceNotEmptyError si projets rattachésPas de cascade automatique. L’utilisateur doit explicitement déplacer ou supprimer ses projets avant.
L’UI WorkspaceDeleteTunnel :
- Affiche la liste des projets rattachés si non-vide
- Désactive le bouton “Supprimer” tant que
attachedProjects.length > 0 - Propose un raccourci “Aller aux projets” pour gérer
6. API serveur
Section intitulée « 6. API serveur »Workspaces
Section intitulée « Workspaces »GET /api/workspaces → listeGET /api/workspaces/:id → détailPOST /api/workspaces → créePUT /api/workspaces/:id → patchDELETE /api/workspaces/:id → supprime (refuse si non-vide)GET /api/workspaces/:id/projects → projets de ce workspaceProjects
Section intitulée « Projects »GET /api/projects → liste tous projetsGET /api/projects/:id → détailGET /api/projects/find-by-cwd?path=... → détection auto par markerPOST /api/projects → crée (vérifie allowlist + workspace existe)PUT /api/projects/:id → patch (path immuable)DELETE /api/projects/:id → forget (204)DELETE /api/projects/:id?purge=true → purge complet, renvoie {deletedPaths}POST /api/projects/:id/touch → touchLastUsed (200 + DTO)Tunnels (création / édition / suppression)
Section intitulée « Tunnels (création / édition / suppression) »| Tunnel | Steps | Périmètre |
|---|---|---|
WorkspaceTunnel | 3 (nom → description → confirm) | Création |
WorkspaceEditTunnel | 3 | Édition |
WorkspaceDeleteTunnel | 2 (vérification non-vide → confirm) | Suppression bloquante si projets |
ProjectTunnel | 5 (path → name → desc → workspace → confirm) | Création avec FolderPicker + auto-suggest cwd serveur + alerte conflit |
ProjectEditTunnel | 4 (name → desc → workspace → confirm) | Édition (path immuable) |
ProjectDeleteTunnel | 3 (mode forget/purge → confirm → résultat) | Suppression avec choix mode + récap deletedPaths |
Tous les tunnels exposent l’overlay loader bloquant pendant les mutations longues (busy={isSubmitting}).
- HomeView : grille des projets du workspace courant + bandeau header workspace (Modifier / Supprimer accessibles directement).
- ProjectView : détail d’un projet — agents installés, sessions chat, actions (Modifier projet / Options de lancement / Supprimer ce projet).
- ContextBand (top bar) : sélecteur workspace + sélecteur projet + actions Edit/Delete dans le footer du dropdown workspace.
8. Events bus
Section intitulée « 8. Events bus »Le module projets/workspaces publie sur le bus arka-deck :
| Event | Mode | Payload |
|---|---|---|
workspace.created | async | { workspaceId, name, createdAt } |
workspace.updated | async | { workspaceId, name, description } |
workspace.deleted | sync | { workspaceId } |
project.created | async | { projectId, projectPath, workspaceId, name } |
project.updated | async | { projectId, name, workspaceId } |
project.forgotten | sync | { projectId } |
project.purged | sync | { projectId, projectPath, deletedPaths } |
project.last-used | async | { projectId, lastUsedAt } |
project.detected-by-cwd | async | { projectId, projectPath, cwd } |
Subscribers prévus : mémoire (cleanup sessions au purge), gouvernance (audit créations), squad (recompose si projet ajouté).
9. Comment ajouter un cleanup au purge
Section intitulée « 9. Comment ajouter un cleanup au purge »Si votre module pose des fichiers dans <projectPath> (autres que .arka-deck/ qui est nettoyé d’office), implémentez un nettoyage côté purgeProjectUseCase.
Option A — Subscriber project.purged (recommandé)
Section intitulée « Option A — Subscriber project.purged (recommandé) »bus.subscribe('project.purged', async (event) => { await myStore.cleanupProject(event.projectPath);});Option B — Étendre purgeProjectUseCase
Section intitulée « Option B — Étendre purgeProjectUseCase »Modifier core/use-cases/projects/build-for-projects.ts pour accepter un nouveau dep optionnel et appeler son cleanup. Préférer Option A pour respecter le découplage.
10. Sécurité
Section intitulée « 10. Sécurité »| Garantie | Mécanisme |
|---|---|
| Pas de path traversal | pathPolicy.isAllowed(path) côté route HTTP avant écriture marker |
| Allowlist utilisateur | ~/.arka-deck/preferences/security.json — patterns globs validés |
Pas de write hors .arka-deck/ au create | Le use-case ne touche que <projectPath>/.arka-deck/project/marker.json |
| Purge ne touche pas aux fichiers utilisateur | Cleanup limité aux items dans installed.json (track explicite) |
| Atomicité écriture marker + index | tmp + rename POSIX |
| Détection corruption | MarkerCorruptedError HTTP 422 — le marker est inutilisable, l’index reste source |
11. Anti-patterns à éviter
Section intitulée « 11. Anti-patterns à éviter »Lire le marker côté UI
Section intitulée « Lire le marker côté UI »Le marker disque n’est pas exposé par les routes HTTP. L’UI consomme uniquement les DTOs serveur via /api/projects (l’index agrégé).
Bypasser findByCwd
Section intitulée « Bypasser findByCwd »// ❌const proj = await projectIndexStore.list().find((p) => p.path === cwd);
// ✅const proj = await forProjects.findByCwd(cwd);Modifier le path d’un projet
Section intitulée « Modifier le path d’un projet »Le path est immuable côté Project.create. Pour changer le path :
forgetl’ancien projet (préserve le marker)createun nouveau projet au nouveau path- Migrer manuellement les fichiers
.arka-deck/si besoin
Supprimer un workspace en cascade silencieuse
Section intitulée « Supprimer un workspace en cascade silencieuse »// ❌await forWorkspaces.delete(wid);forceDeleteAll(wid);
// ✅const projects = await forProjects.listByWorkspace(wid);await Promise.all(projects.map((p) => forProjects.forget(p.id)));await forWorkspaces.delete(wid);Trust de l’index sans le marker
Section intitulée « Trust de l’index sans le marker »Pour la vérité absolue sur l’état d’un projet, lire le marker disque (projectStore.load(path)). L’index peut être désynchronisé.
12. Référence rapide
Section intitulée « 12. Référence rapide »| Action | Fichier |
|---|---|
| Domain Project | core/domain/project/project.ts |
| Domain Workspace | core/domain/workspace/workspace.ts |
| Use-cases projets | core/use-cases/projects/build-for-projects.ts |
| Use-cases workspaces | core/use-cases/workspaces/build-for-workspaces.ts |
| Marker store | adapters/outbound/filesystem/fs-project-store.ts |
| Index store | adapters/outbound/filesystem/fs-project-index-store.ts |
| Workspace store | adapters/outbound/filesystem/fs-workspace-store.ts |
| Path policy | adapters/outbound/system/preferences-path-policy.ts |
| Routes serveur | adapters/inbound/web/server/src/routes/{projects,workspaces}.ts |
| Tunnels UI | adapters/inbound/web/ui/src/components/tunnel/*Tunnel.tsx |
| Hooks UI | adapters/inbound/web/ui/src/hooks/{useProjects,useWorkspaces}.ts |
| Errors | core/domain/errors.ts |