Files
flowdeck/docs/agents-skills-phase-1-skill-page.md
T
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

367 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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": "<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
```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.*