feat(fusion): format commun des skills (ACC-4.5), pilote réunion → ObsiGate (ACC-6.2), contrat panneau (ACC-4.4) — 7.84.0
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
"""Fusion ACC-4.5 — conversion d'Agents & Skills Flowdeck au format
|
||||
commun (contrat ACC-0.3, `dracodev-commons/contrats/skill-format.md`).
|
||||
|
||||
Preuve du format : trois presets de la galerie Flowdeck convertis en
|
||||
`skills-communs/<nom>/SKILL.md` (compte-rendu-reunion, rapport-hebdo,
|
||||
resume-document). Ils doivent être conformes au contrat, se convertir
|
||||
en champs Flowdeck et s'installer dans un workspace via le chemin
|
||||
existant (`skill_gallery.upsert_skill`).
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services import skill_format, skill_gallery
|
||||
|
||||
RACINE = Path(__file__).resolve().parent.parent
|
||||
CONVERTIS = ["compte-rendu-reunion", "rapport-hebdo", "resume-document"]
|
||||
|
||||
|
||||
def _lire(nom: str) -> str:
|
||||
return (RACINE / "skills-communs" / nom / "SKILL.md").read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def test_trois_skills_convertis_conformes():
|
||||
for nom in CONVERTIS:
|
||||
skill = skill_format.analyser_skill_md(_lire(nom))
|
||||
assert skill_format.valider(skill) == [], (nom, skill_format.valider(skill))
|
||||
assert skill["meta"]["name"] == nom
|
||||
assert skill["meta"]["apps"] == ["flowdeck"]
|
||||
assert skill["meta"]["version"] == "1.0.0"
|
||||
# Toute écriture est déclarée, avec approbation explicite.
|
||||
actions = {e["action"] for e in skill["meta"]["ecritures"]}
|
||||
assert actions == {"flowdeck.create_document", "flowdeck.write_blocks"}
|
||||
|
||||
|
||||
def test_conversion_vers_flowdeck_et_installation(client):
|
||||
for nom in CONVERTIS:
|
||||
skill = skill_format.analyser_skill_md(_lire(nom))
|
||||
champs = skill_format.vers_champs_flowdeck(skill)
|
||||
assert champs["name"] == nom
|
||||
assert set(champs["allowed_tools"]) == {
|
||||
"search_workspace", "read_document", "create_document", "write_blocks"}
|
||||
assert "Procédure" in champs["prompt_template"]
|
||||
assert "Garde-fous" in champs["prompt_template"]
|
||||
row, cree = skill_gallery.upsert_skill(
|
||||
champs, workspace_id=None, created_by=1)
|
||||
assert cree is True and row["name"] == nom
|
||||
|
||||
|
||||
def test_validation_rejette_les_ecarts():
|
||||
incomplet = skill_format.analyser_skill_md(
|
||||
"---\nname: mauvais nom\napps: [flowdeck]\n---\n\n## Procédure\n\nx\n")
|
||||
erreurs = skill_format.valider(incomplet)
|
||||
assert any("kebab" in e for e in erreurs)
|
||||
assert any("description" in e for e in erreurs)
|
||||
assert any("Quand l'utiliser" in e for e in erreurs)
|
||||
with pytest.raises(ValueError):
|
||||
skill_format.vers_champs_flowdeck(incomplet)
|
||||
|
||||
|
||||
def test_ecritures_obligatoires_si_outil_ecrivant():
|
||||
texte = (
|
||||
"---\nname: supprime-tout\ndescription: x\nversion: 1.0.0\n"
|
||||
"apps: [flowdeck]\ndomaines: [pro]\noutils: [flowdeck.delete_page]\n---\n\n"
|
||||
"## Quand l'utiliser\n\nx\n\n## Procédure\n\nx\n\n## Garde-fous\n\nx\n")
|
||||
erreurs = skill_format.valider(skill_format.analyser_skill_md(texte))
|
||||
assert any("ecritures obligatoire" in e for e in erreurs)
|
||||
|
||||
|
||||
def test_aller_retour_depuis_un_preset():
|
||||
preset = skill_gallery.get_gallery("compte-rendu-reunion")
|
||||
texte = skill_format.depuis_preset_flowdeck(preset, domaines=["pro"])
|
||||
skill = skill_format.analyser_skill_md(texte)
|
||||
assert skill_format.valider(skill) == []
|
||||
champs = skill_format.vers_champs_flowdeck(skill)
|
||||
assert champs["name"] == "compte-rendu-de-reunion"
|
||||
assert "create_document" in champs["allowed_tools"]
|
||||
Reference in New Issue
Block a user