# Agents & Skills pour Flowdeck — Phase 1 : Le Skill devient une page | Champ | Valeur | |---|---| | Phase | **1 sur 5** — fondation de toute la solution | | Document parent | `architecture-agents-skills-notion-flowdeck.md` v1.1 — en particulier §3.4 (les Skills dans Notion), §4.2 (écarts E1, E3, E4), §10.3–10.4, §13.1, §13.4–13.6 | | Version | 1.0 — 9 octobre 2026 | | Auteur | Spark, pour Bruno | | Statut | Prêt à implémenter | | Prérequis | Flowdeck v7.69.8 ; migration courante = 38 | | Migration créée | **39** | | Dépendances | Aucune (première phase) | | Débloque | Phases 2 (routeur de skills), 3 (skills des Custom Agents), 5 (Skills API) | > **Résultat visible à la fin de cette phase.** N'importe quelle page Flowdeck peut devenir un skill ; une collection peut devenir une base de skills avec `Description` / `Files` / `Tags` ; la Library a un onglet *Skills* et un onglet *Discover* avec `Enable for me` ; les skills s'exécutent depuis le chat (`/`), le menu de sélection, le menu slash et le menu de bloc via un moteur unique (le Skill Runner) ; les skills intégrés d'AI Writing sont exposés comme skills ; chaque skill s'exporte en `SKILL.md` téléchargeable pour les agents locaux. Aucun skill existant n'est perdu : les enregistrements actuels et les 17 presets deviennent des pages. --- ## Table des matières 1. [Objectif et principe directeur](#1-objectif-et-principe-directeur) 2. [Périmètre](#2-périmètre) 3. [État d'entrée — ce qui existe en v7.69.8](#3-état-dentrée--ce-qui-existe-en-v7698) 4. [Modèle de données — migration 39](#4-modèle-de-données--migration-39) 5. [Tickets de travail](#5-tickets-de-travail) 6. [API livrée par la phase](#6-api-livrée-par-la-phase) 7. [Comportements détaillés](#7-comportements-détaillés) 8. [Plan de migration des données existantes](#8-plan-de-migration-des-données-existantes) 9. [Tests](#9-tests) 10. [Critères d'acceptation](#10-critères-dacceptation) 11. [Risques spécifiques et vigilance](#11-risques-spécifiques-et-vigilance) 12. [Ordre d'exécution](#12-ordre-dexécution) 13. [Définition de « terminé »](#13-définition-de--terminé-) --- ## 1. Objectif et principe directeur Le principe (ADR-01 du document parent) : **la page est la source de vérité du skill ; la table `agent_skills` devient son index d'exécution.** Toute l'économie de la phase vient de ce que Flowdeck n'a pas à construire pour un skill ce qu'il a déjà pour une page : éditeur de blocs, ACL (partage, groupes, héritage), historique `page_versions`, corbeille, recherche FTS5 et sémantique, commentaires, Library. Corollaires qui guident chaque ticket : - **Marquer une page ne la déplace pas et ne la copie pas.** C'est l'insertion d'une ligne d'index. Démarquer supprime la ligne, la page ne bouge pas. - **Un skill de base est une ligne de collection comme les autres** : son contenu est la page-ombre (`pages.collection_row_id`), ses métadonnées (`Description`, `Files`) sont des propriétés de la ligne. - **Aucun nouvel éditeur, aucun nouveau modèle de permissions.** Si un comportement de skill ne peut pas s'exprimer avec les mécanismes de page/collection existants, c'est un signal d'arrêt et de redesign, pas une raison d'ajouter un sous-système. ## 2. Périmètre ### 2.1 Inclus - Migration 39 (refonte `agent_skills`, `collections.is_skills_db`, `user_skill_enablements`, `skill_runs`, `skill_local_downloads`). - Marquer/démarquer une page comme skill ; déplacer une page dans une base de skills. - Créer une base de skills (gabarit) ; convertir une collection existante en base de skills (correspondance des propriétés). - Bannière de skill sur la page (états, interrupteurs, téléchargement, badge de copie périmée). - Library : onglet *Skills* et sous-vue *Discover*, `Enable for me`, désactivation pour soi. - Skill Runner : chargement d'un skill (page + fichiers), construction de la consigne, exécution manuelle depuis 4 surfaces (chat `/`, sélection, slash, bloc), journalisation dans `skill_runs`. - Skills intégrés : améliorer l'écriture, corriger, expliquer, reformater, traduire, résumer. - Création assistée d'un skill par l'Agent (dialogue → création de la page). - Export `SKILL.md` (+ fichiers partageables) et import `SKILL.md` ; format `flowdeck-skill` v1 conservé. - Migration des skills existants et des 17 presets en pages. ### 2.2 Exclu (phases ultérieures) - L'**usage automatique** des skills par l'Agent (routage sur la `Description`) → Phase 2. La `Description` est collectée et indexée dès cette phase, mais rien ne l'exploite encore automatiquement. - L'usage des skills par les **Custom Agents** (accès accordés, runs autonomes) → Phase 3. - La **comptabilité** d'usage détaillée par run (`agent_runs`, crédits) → Phases 2 et 4. Ici, `skill_runs` journalise avec les références disponibles (conversation, surface, utilisateur) ; la colonne `run_id` définitive arrive en Phase 2 — voir §4.3 pour la parade. - L'écriture automatique dans les répertoires des agents locaux (utilitaire compagnon) → question ouverte Q4 du document parent ; cette phase livre le **téléchargement** du bundle et le suivi d'empreinte. ## 3. État d'entrée — ce qui existe en v7.69.8 | Élément | État | Fichier / mécanisme (architecture v7.69.8, §16.3) | |---|---|---| | Skills actuels | Prompts paramétrés + outils autorisés, table `agent_skills` | `app/services/skill_gallery.py` | | Galerie | 17 presets installables | `skill_gallery.py`, routes `/api/agent/skills` | | Format portable | `flowdeck-skill` v1, export/import JSON | idem + `/api/v2/skills/*` | | Points d'entrée éditeur | Menu contextuel de bloc → *Skills ›* ; toolbar de sélection → *Skills* (§13.3) ; section Compétences du Menu + (gérer/parcourir) | `page_editor_scripts.js`, `agent_panel_*.js` | | AI Writing | 6 actions headless (écrire, résumer, traduire, continuer, autocomplétion, propriétés) | `app/services/ai_writing.py` | | Page-ombre de ligne | Chaque ligne de collection peut porter une page de contenu | `pages.collection_row_id` (v6.5) | | Indexation sémantique | Embeddings maison, index incrémental, `resource_type` libre | `app/services/semantic_search.py` | ## 4. Modèle de données — migration 39 ### 4.1 Schéma (repris du document parent §10.3–10.4, reséquencé) ```sql -- Migration 39 — Phase 1 ALTER TABLE collections ADD COLUMN is_skills_db INTEGER NOT NULL DEFAULT 0; -- Correspondance des propriétés dans collections.schema_json : -- {"skill_properties": {"description": "", "files": "", "tags": ""}} -- agent_skills est RECRÉÉE (patron : nouvelle table, copie, bascule, §8) CREATE TABLE agent_skills_new ( id INTEGER PRIMARY KEY, page_id INTEGER NOT NULL UNIQUE REFERENCES pages(id) ON DELETE CASCADE, collection_id INTEGER REFERENCES collections(id), name_cached TEXT NOT NULL, description TEXT DEFAULT '', files_json TEXT DEFAULT '[]', tools_json TEXT DEFAULT '[]', is_builtin INTEGER NOT NULL DEFAULT 0, auto_use_default INTEGER NOT NULL DEFAULT 1, editor_menu_default INTEGER NOT NULL DEFAULT 0, owner_id INTEGER REFERENCES users(id), status TEXT DEFAULT 'ready', source TEXT DEFAULT 'page', -- 'page' | 'legacy' | 'import' | 'builtin' created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE user_skill_enablements ( user_id INTEGER NOT NULL REFERENCES users(id), skill_id INTEGER NOT NULL REFERENCES agent_skills_new(id) ON DELETE CASCADE, enabled INTEGER NOT NULL DEFAULT 1, auto_use INTEGER, editor_menu INTEGER, created_at TEXT NOT NULL, PRIMARY KEY (user_id, skill_id) ); CREATE TABLE skill_runs ( id INTEGER PRIMARY KEY, skill_id INTEGER NOT NULL REFERENCES agent_skills_new(id), run_id INTEGER, -- nullable en Phase 1, rattaché en Phase 2 (§4.3) conversation_id INTEGER REFERENCES agent_conversations(id), user_id INTEGER REFERENCES users(id), surface TEXT NOT NULL, -- 'chat' | 'editor' | 'agent' | 'api' invocation TEXT NOT NULL, -- 'manual' en Phase 1 ('auto' dès la Phase 2) created_at TEXT NOT NULL ); CREATE TABLE skill_local_downloads ( user_id INTEGER NOT NULL REFERENCES users(id), skill_id INTEGER NOT NULL REFERENCES agent_skills_new(id) ON DELETE CASCADE, target TEXT NOT NULL, -- 'claude-code'|'codex'|'cursor'|'gemini'|'grok'|'file' page_version TEXT NOT NULL, -- empreinte au téléchargement downloaded_at TEXT NOT NULL, PRIMARY KEY (user_id, skill_id, target) ); ``` ### 4.2 Règles du schéma 1. `page_id UNIQUE` : un skill = une page, sans exception. Le marquage est idempotent (un second marquage renvoie le skill existant). 2. `ON DELETE CASCADE` depuis `pages` : mettre la page à la corbeille **purge dure** ou la suppression effective retire le skill partout (menus, Library) — comportement attendu, testé (T-19). 3. Pour un skill de base, `description` et `files_json` sont des **caches relus** depuis les propriétés de la ligne à chaque sauvegarde de la ligne ou de sa page-ombre ; la ligne de collection fait foi. 4. Pour un skill autonome, `description`/`files_json` sont édités depuis la bannière et stockés ici. 5. `tools_json` hérite du modèle actuel : un skill peut **restreindre** les outils d'un run, jamais les élargir (intersection appliquée par le moteur, §7.4). ### 4.3 La parade `run_id` `agent_runs` n'existe qu'en Phase 2. Pour ne pas bloquer la Phase 1 : `skill_runs.run_id` est nullable, `conversation_id` + horodatage portent la traçabilité immédiate ; la Phase 2 ajoute la contrainte de rattachement par une migration de données (et non de schéma). Ne pas créer de table `runs` temporaire. ### 4.4 Indexation À la création/modification d'un skill : écrire ou mettre à jour les lignes `semantic_embeddings` avec `resource_type='skill'`, contenu = `name + description` (le titre seul ne suffit pas à l'appariement futur). Le scheduler d'indexation existant (300 s) est le filet ; l'écriture directe à la sauvegarde donne la fraîcheur nécessaire aux menus. ## 5. Tickets de travail *Convention : chaque ticket est indépendamment testable ; « Fichiers » donne les points d'ancrage d'après l'architecture v7.69.8 — à confirmer contre le code réel au démarrage du ticket.* ### Socle données **P1-T01 — Migration 39 et recréation d'`agent_skills`.** Fichiers : `app/migrations.py` (`@register(39, ...)`), `app/db.py` si la baseline doit refléter le nouvel état pour les installations neuves. Travail : créer le schéma du §4 ; la copie des données existantes est dans le même ticket que le script de migration de contenu (P1-T02), exécuté comme étape de la migration (données) — la migration reste une transaction unique. Acceptation : sur une base de test v7.69.8 complète, `apply_migrations()` passe à la version 39 sans perte (comptages avant/après identiques pour les skills convertis, §8). **P1-T02 — Migration de contenu : skills existants → pages.** Travail : pour chaque ancien skill sans page : créer une page (workspace de l'utilisateur, privée par défaut, dans une base de skills privée créée à la demande — voir P1-T04), y écrire le prompt d'origine **à l'identique** comme corps de page, créer la ligne `agent_skills` avec `source='legacy'`, conserver `tools_json`. Les presets de la galerie ne sont **pas** matérialisés pour tous : ils le sont à l'installation (P1-T13). Acceptation : chaque ancien skill reste invocable après migration (test de non-régression sur le jeu de skills de la base de test) ; aucun prompt modifié (comparaison de hash avant/après). **P1-T03 — Service `skills_pages.py` : le cycle de vie page ↔ skill.** Fichiers : nouveau `app/services/skills_pages.py` ; évolution de `skill_gallery.py` (qui devient un consommateur du service, pas le propriétaire du modèle). Travail : `mark_as_skill(page_id, user)`, `unmark(page_id)`, `get_skill_by_page(page_id)`, `sync_skill_from_row(collection_page)` (recopie Description/Files depuis les propriétés), `sync_skill_from_page(page)` (titre, cache), suppression en cascade déjà portée par le schéma. Tous les appels vérifient l'ACL de la page via `PermissionManager` (marquer exige l'édition de la page). Acceptation : les cinq opérations sont couvertes en tests unitaires, y compris les refus d'ACL. ### Bases de skills **P1-T04 — Base de skills privée par défaut.** Travail : à la première création d'un skill par un utilisateur sans base, créer sa collection `is_skills_db=1` privée (« Mes skills »), avec les propriétés `Description` (text), `Files` (files), `Tags` (multi_select) créées dans le schéma de la collection. Idempotent par utilisateur. Acceptation : deux créations successives de skills par le même utilisateur n'ont créé qu'une base. **P1-T05 — Gabarit « Skills » et conversion d'une collection.** Fichiers : gabarits de bases (`db_templates`/seed existant), route `PUT /db/{id}/skills-db` (§6). Travail : création directe d'une base de type Skills (mêmes propriétés qu'en T04 + vue par défaut triée par nom) ; dialogue de conversion : mapper des propriétés existantes vers `Description`/`Files`/`Tags` ou les créer ; option « activer les pages existantes comme skills pour moi » ; la conversion pose `is_skills_db=1` et crée les lignes `agent_skills` des lignes de la base (page-ombre requise : la créer si absente). Acceptation : convertir la base de test « Playbooks » transforme chaque ligne en skill visible dans la Library ; la déconversion (`is_skills_db=0`) retire les lignes d'index sans toucher aux pages (règle du §18 du document parent). **P1-T06 — Marquage depuis une page ordinaire.** Fichiers : menu `•••` de page (gabarits de l'éditeur), route `POST /api/agent/pages/{id}/skill`. Travail : entrée *Use as a skill* (case à cocher, état relu) ; si la page n'est dans aucune base de skills, le skill est autonome (description éditée via la bannière, T07) ; proposer en un clic « déplacer dans une base de skills » (réutilise le *Move to* existant + création de la ligne dans la base cible). Acceptation : marquer, démarquer, re-marquer la même page donne un seul skill à chaque état stable. ### Expérience du skill **P1-T07 — Bannière de skill.** Fichiers : fragment de l'éditeur de page (au-dessus du corps, comme les bannières existantes), JS de l'éditeur. Contenu : badge « Skill », base d'appartenance (lien), état d'activation pour l'utilisateur, interrupteurs *Use automatically* (défaut du skill) et *Add to text editor menu*, champ `Description` (skill autonome) ou lien vers la propriété (skill de base), bouton *Download for local agents*, badge orange « copie locale périmée » si une ligne `skill_local_downloads` de l'utilisateur est en retard d'empreinte. Acceptation : la bannière n'apparaît que sur les pages-skills ; ses interrupteurs écrivent les bonnes colonnes (défaut du skill vs choix de l'utilisateur — ne pas confondre, §7.2). **P1-T08 — Library : onglets Skills et Discover.** Fichiers : `app/routers/library.py`, `app/templates/library.html`, `static/js/library.js`. Travail : onglet *Skills* (skills créés + activés ; colonnes du §7.5 du parent : Nom, Base, Description, Use automatically, Menu éditeur, Propriétaire, Dernière utilisation depuis `skill_runs`) ; sous-vue *Discover* (skills lisibles non activés) avec bouton `Enable for me` par ligne ; recherche par nom/description ; les interrupteurs de l'onglet écrivent `user_skill_enablements`. Acceptation : un skill partagé avec moi apparaît dans Discover, pas dans Skills ; après `Enable for me`, il bascule ; le désactiver le retire de mes menus sans affecter les autres utilisateurs (test multi-utilisateurs). **P1-T09 — Le Skill Runner.** Fichiers : nouveau `app/services/skill_runner.py`. Travail : `load_skill(skill_id, user)` → {corps Markdown de la page (blocs éligibles sérialisés comme le fait le Context Builder), fichiers extraits (texte des fichiers `shareable`, plafonnés par budget), tools_json} ; `run(skill_id, input, surface, user, conversation_id)` → construit la consigne (page du skill en couche « consigne de tâche »), appelle le moteur existant en run contraint (outils = intersection), écrit `skill_runs`, renvoie la sortie structurée {texte, blocs, skill_name}. Mode LLM `offline` : sortie déterministe de test. Acceptation : un run de skill produit une ligne `skill_runs` complète ; le nom du skill est présent dans la réponse rendue (préparation de l'invariant I5). **P1-T10 — Invocation dans le chat : menu `/`.** Fichiers : `agent_panel_*.js`, route `GET /api/agent/skills/menu`, compositeur du panneau. Travail : taper `/` ouvre la liste (skills activés de l'utilisateur, recherche par nom) ; `/nom` filtre directement ; `+` lance la création assistée (T14) ; sélectionner un skill l'attache au prochain message (pastille retirable) et le run passe par le Runner (T09). Acceptation : `/` sans skill activé affiche les intégrés + l'entrée de création ; un skill forcé par `/` n'est jamais remplacé par un autre (le routeur automatique n'existe pas encore, mais le contrat est posé). **P1-T11 — Invocation dans l'éditeur : sélection, slash, bloc.** Fichiers : `page_editor_scripts.js` (les trois points d'entrée existent — les rebrancher sur le Runner), registre des types de blocs. Travail : déclarer `skill_eligible` par type de bloc (liste du document parent §13.4) ; menu de sélection : skills avec `editor_menu` effectif d'abord, intégrés ensuite, entrée *Manage Skills* ; menu de bloc → *Skills* (bloc + contenu imbriqué) ; slash `/skill` uniquement sur ligne non vide ; résultat en **aperçu diff** pour les transformations (accepter/refuser/réessayer), insertion sous le bloc pour les générations. Acceptation : les trois gestes produisent le même résultat à entrée égale ; un bloc non éligible ou vide n'offre pas de skills. **P1-T12 — Skills intégrés sur AI Writing.** Fichiers : `app/services/ai_writing.py`, seed des skills intégrés. Travail : créer les 6 pages-skills intégrées (`is_builtin=1`, `source='builtin'`, workspace système ou seed par utilisateur — décision : seed **par workspace**, page système non éditable, désactivable par utilisateur via les enablements) : Améliorer l'écriture, Corriger, Expliquer, Reformater, Traduire, Résumer — chacune appelle le service AI Writing existant via le Runner plutôt que de dupliquer les prompts ; l'autocomplétion inline reste sur le service direct (latence). Acceptation : le menu de sélection d'un nouvel utilisateur affiche exactement les 6 intégrés, dans l'ordre ; désactiver « Corriger » le retire pour cet utilisateur seul. ### Portabilité **P1-T13 — Galerie rebranchée sur les pages.** Travail : installer un preset = créer la page-skill correspondante dans la base privée de l'utilisateur (corps = le contenu du preset, `Description` rédigée pour un futur appariement — relire les 17 descriptions pendant ce ticket) + enablement activé. La galerie existante devient une vue de presets-pages ; l'ancien format d'installation est abandonné après migration (T02). Acceptation : réinstaller les 17 presets sur un compte de test donne 17 pages éditables ; modifier la page modifie le comportement du skill au run suivant. **P1-T14 — Création assistée par l'Agent.** Travail : depuis le chat (`/` → `+` ou « crée un skill qui… »), un run guidé de l'Agent personnel pose les questions minimales (travail, entrées, règles, exemples, sortie) puis appelle un outil interne de création de page-skill dans la base privée ; l'utilisateur est amené sur la page créée pour relire. Acceptation : le parcours complet produit un skill invocable sans quitter le chat autrement que pour la relecture. **P1-T15 — Export `SKILL.md` et suivi de copie locale.** Fichiers : `app/services/skill_export.py` (nouveau), route `GET /api/agent/skills/{id}/download`. Travail : sérialisation Markdown (frontmatter `name`/`description` + corps de page) ; bundle ZIP = `SKILL.md` + fichiers `shareable=1` ; écriture d'une ligne `skill_local_downloads` (target `file` par défaut, cible nommée si choisie) avec l'empreinte = hash du contenu sérialisé + `updated_at` de la page ; l'empreinte courante est recalculée à la lecture pour le badge (T07). Acceptation : le `SKILL.md` exporté est relu par le test d'import (T16) sans perte de nom/description/corps. **P1-T16 — Import `SKILL.md` et compatibilité v1.** Travail : `POST /api/agent/skills/import` accepte un `SKILL.md` (frontmatter analysé) ou un bundle, crée une page-skill (`source='import'`) dans la base privée, range les fichiers joints en propriété `Files` ; le format `flowdeck-skill` v1 reste accepté et produit le même résultat (page + ligne d'index, `tools_json` conservé). Acceptation : les trois sources (page existante, `SKILL.md` externe, JSON v1) convergent vers le même objet skill. ### Finitions **P1-T17 — API v2 des skills alignée.** Travail : `GET /api/v2/skills` et `/{id}` exposent `page_id`, `description`, `collection_id` ; négociation de contenu : `Accept: text/markdown` renvoie la sérialisation `SKILL.md` ; le JSON v1 reste le défaut (compatibilité des clients existants). Acceptation : les tests API v2 actuels passent inchangés ; un test nouveau couvre la variante Markdown. **P1-T18 — Documentation in-app et aide.** Travail : entrée d'aide du produit (section Skills : page, base, Library, Discover, agents locaux), textes de la bannière et des dialogues en français, noms d'objets en anglais conformément au document parent. Acceptation : revue des libellés (pas de « compétence » imposé dans l'UI là où le produit dit *Skill*). ## 6. API livrée par la phase | Route | Ticket | |---|---| | `POST /api/agent/pages/{id}/skill` · `DELETE …` | T06 | | `PUT /db/{id}/skills-db` (conversion) | T05 | | `GET /api/agent/skills/menu` | T10 | | `PUT /api/agent/skills/{id}/enablement` | T08 | | `POST /api/agent/skills/{id}/run-chat` · `POST …/run-editor` | T09–T11 | | `GET /api/agent/skills/{id}/download` | T15 | | `POST /api/agent/skills/import` | T16 | | `GET /api/v2/skills` · `GET /api/v2/skills/{id}` (étendus) | T17 | Toutes les routes internes suivent les conventions existantes : cookie-auth, CSRF complet, contrôle d'ACL dans le routeur, erreurs au format du produit. ## 7. Comportements détaillés ### 7.1 Qui peut faire quoi | Action | Droit requis | |---|---| | Marquer une page | Éditer la page | | Éditer un skill | Éditer sa page (rien de plus, rien de moins) | | Activer un skill pour soi | Lire sa page | | Exécuter un skill | Lire sa page + être activé (ou être son créateur) | | Convertir une base | Admin/owner de la collection | | Désactiver le skill d'un autre | Impossible : la désactivation est toujours « pour soi » ; retirer pour tous = supprimer la page ou la démarquer par un éditeur | ### 7.2 Défaut du skill vs choix de l'utilisateur Deux niveaux, à ne jamais écraser l'un par l'autre : les colonnes `*_default` d'`agent_skills` (posées par le propriétaire) et les surcharges nullable de `user_skill_enablements` (posées par chaque utilisateur). Résolution : surcharge utilisateur si non NULL, sinon défaut du skill. La bannière édite le **défaut** (si l'utilisateur peut éditer la page) ; l'onglet Library édite la **surcharge**. ### 7.3 Contenu éligible d'un bloc de skill Le corps du skill est la page entière sérialisée, mais l'**entrée** d'un run éditeur respecte l'éligibilité (§13.4 du parent) : types listés seulement, contenu imbriqué inclus, blocs vides ignorés. Le Runner reçoit l'entrée déjà filtrée — il ne connaît pas l'éditeur. ### 7.4 Contrainte d'outils `outils_du_run = outils_autorisés_par_la_politique ∩ (outils_du_skill si tools_json non vide, sinon tous)`. Un skill ne peut ni ajouter un outil MCP, ni lever une approbation. Test dédié obligatoire (T-19). ### 7.5 Le skill supprimé en cours de route Si la page part à la corbeille entre l'affichage du menu et le run : le Runner répond par une erreur propre « skill indisponible », le menu se rafraîchit, aucun run fantôme dans `skill_runs`. ## 8. Plan de migration des données existantes 1. **Sauvegarde** : snapshot SQLite par le service de backup existant avant le déploiement (procédure habituelle des migrations risquées). 2. **Inventaire** : compter les skills existants par utilisateur et par type ; photographier les hashes des prompts. 3. **Migration 39** : schéma + T02 en une transaction ; en cas d'échec, rien n'est appliqué (patron des migrations Flowdeck). 4. **Vérification post-migration** (script jetable, pas de code livré) : comptages avant/après, hashes des corps de pages générées = hashes des prompts d'origine, échantillon de 5 skills invoqués en mode offline. 5. **Bascule d'UI** : les anciens écrans de gestion des skills (galerie enregistrements) sont retirés dans le même déploiement ; pas de période de double modèle. ## 9. Tests *Pyramide existante : pytest (TestClient, base temporaire), Playwright pour les gestes réels. Tous les tests de skill doivent passer en mode LLM `offline`.* | # | Niveau | Cas | |---|---|---| | T-01 | Unitaire | Marquer/démarquer : idempotence, unicité `page_id`, refus sans droit d'édition | | T-02 | Intégration | Migration 39 sur fixture v7.69.8 : comptages, hashes, skills legacy invocables | | T-03 | Intégration | Conversion d'une collection : propriétés mappées ou créées, lignes → skills, déconversion propre | | T-04 | Intégration | Skill de base : modifier la propriété `Description` de la ligne met à jour le cache du skill | | T-05 | Intégration | Discover/Enable : visibilité par ACL, activation personnelle, désactivation sans effet sur autrui | | T-06 | Intégration | Runner : ligne `skill_runs` écrite ; nom du skill dans la sortie ; intersection d'outils respectée | | T-07 | Intégration | Un skill dont `tools_json` tente d'ajouter un outil hors politique : l'outil n'est pas disponible au run | | T-08 | Intégration | Export `SKILL.md` → import : nom, description, corps et fichiers conservés | | T-09 | Intégration | Badge de copie périmée : téléchargement, puis édition de la page → badge présent ; re-téléchargement → badge absent | | T-10 | E2E | Sélectionner un texte → menu Skills → Corriger → diff → accepter : le texte est remplacé, l'historique de page contient la version | | T-11 | E2E | Chat : `/` → choisir un skill → pastille visible → réponse produite | | T-12 | E2E | Supprimer la page d'un skill affiché dans un menu ouvert : erreur propre au run, pas d'écran cassé | | T-13 | Non-régression | Les tests existants des routes skills (cookie et v2) passent ; le format v1 s'importe toujours | ## 10. Critères d'acceptation - [ ] Sur une installation migrée, l'inventaire des skills avant/après est identique (noms, prompts) et chaque skill est devenu une page éditable. - [ ] Un playbook existant (page ordinaire) devient un skill invocable en moins de cinq gestes, sans copier-coller. - [ ] Une équipe partage une base de skills ; un membre la découvre dans *Discover*, l'active pour lui, et l'invoque depuis l'éditeur. - [ ] Les quatre surfaces d'invocation (chat `/`, sélection, slash, bloc) passent par le même Runner (vérifiable : elles écrivent le même format de `skill_runs`). - [ ] Un skill exporté en `SKILL.md` se réimporte sans perte ; le badge signale une copie locale périmée après édition de la page. - [ ] Aucun run de skill ne peut utiliser un outil que la politique ou le skill ne lui donne pas. - [ ] Le mode LLM `offline` exécute tous les parcours de test de la phase. ## 11. Risques spécifiques et vigilance | Risque | Vigilance | |---|---| | La recréation d'`agent_skills` casse les clés étrangères existantes (références depuis d'autres tables) | Inventaire des FK entrantes **avant** T01 ; la recréation suit le patron SQLite (désactiver les FK pendant la copie, `foreign_key_check` après) | | Les pages générées par la migration polluent les *Recents* et la recherche des utilisateurs | Marquer les pages migrées avec leur date d'origine ; ne pas toucher `recents` ; la recherche les indexe comme des pages normales (c'est voulu), mais le titre est préfixé du nom du skill d'origine, sans décor | | Deux bases privées créées en concurrence (double premier skill) | Contrainte d'unicité applicative + création dans la même transaction que le premier skill (T04) | | Les descriptions des 17 presets, écrites pour des humains, sont trop vagues pour le futur routeur | T13 impose leur relecture dès cette phase (le routeur de la Phase 2 en dépend) | | Confondre défaut du skill et choix utilisateur dans l'UI | §7.2 + libellés distincts dans la bannière (« pour tous ») et la Library (« pour moi ») | ## 12. Ordre d'exécution ```text T01 ── T02 ── T03 ──┬── T04 ── T05 ── T06 ── T07 ├── T09 ──┬── T10 │ ├── T11 │ └── T12 ├── T08 ├── T13 ── T14 └── T15 ── T16 ── T17 T18 en fil continu ; tests au fur et à mesure, jamais en bloc final. ``` Le chemin critique est T01 → T03 → T09 : tant que le Runner n'existe pas, aucune surface ne peut être rebranchée. ## 13. Définition de « terminé » La phase est terminée quand : les critères du §10 passent sur une base migrée depuis la v7.69.8 **et** sur une installation neuve ; la suite de tests existante est verte ; la documentation in-app (T18) est livrée ; le document parent est annoté (version 1.2 : « Phase 1 livrée », avec les écarts éventuels entre plan et réalisation consignés plutôt que tus). --- *Document de phase 1/5 — voir le document parent pour l'analyse fonctionnelle Notion et l'architecture d'ensemble.*