Files
flowdeck/docs/agents-skills-phase-1-skill-page.md
bruno fc8548194a
FlowDeck CI / lint (push) Failing after 1m32s
FlowDeck CI / test (push) Failing after 27m50s
FlowDeck CI / docker (push) Skipped
feat(templates): refonte complète des templates façon Notion + vues/agents-skills
Templates (v7.71.x) :

- registre unifié \	emplates\ (migrations 48-49) + TemplateService.instantiate unique (UI, API v2, agent, scheduler)

- sélecteur (pilule page vide, menu •••, commande /template), gestionnaire /templates, menu New ▾, From template, base inline dans un document

- 141 presets système (59 pages, 42 bases, 15 blocs, 25 lignes), titre auto depuis le template, variables title réservée

- récurrences RRULE + scheduler dédupliqué, agent apply_template/list_templates, API /api/templates + /api/v2/fd-templates

- correctifs : bouton Templates, centrage fenêtre, filtres CSP, flux de création, variable title

- tests : tests/test_fd_templates.py (19) et e2e/templates_picker.spec.js (8)

Inclut le travail déjà présent dans le working tree (vues Notion : view_query/view_aggregate/form_projection/geocoding, property_types, database_table, docs agents-skills) et ignore .playwright-mcp/.
2026-10-10 18:52:19 -04:00

30 KiB
Raw Permalink Blame History

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
  2. Périmètre
  3. État d'entrée — ce qui existe en v7.69.8
  4. Modèle de données — migration 39
  5. Tickets de travail
  6. API livrée par la phase
  7. Comportements détaillés
  8. Plan de migration des données existantes
  9. Tests
  10. Critères d'acceptation
  11. Risques spécifiques et vigilance
  12. Ordre d'exécution
  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é)

-- 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": "<nom prop>", "files": "<nom prop>", "tags": "<nom prop>"}}

-- 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

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.