7.3 KiB
7.3 KiB
L12 — Skills
Binôme : dev-skills / test-skills
Zones : domain/skill, application/skill, infrastructure/store, frontend/features/skills
Dépendances amont : L0, L1, L5, L6 (convention file généré à l'activation), L7 (store global réutilisé).
Objectif
Modéliser les Skills : workflows réutilisables (équivalent universel des slash-commands, sans dépendance à la syntaxe /command d'un modèle). Stockage global IDE + projet, assignation agent↔skills, injection des skills assignés dans le convention file généré à l'activation de l'agent. Cf. ARCHITECTURE §14.2.
Périmètre (DEV)
- Domaine : entité
Skill { id, name, content_md: MarkdownDoc, scope: SkillScope(Global|Project) }. Invariants :namenon vide,content_mdnon vide. EventSkillAssigned. - Port
SkillStore: CRUD skills globaux (<app_data>/IdeA/skills/) + skills projet (.ideai/skills/<name>.md), résolution selonscope(composeFileSystem/store global comme L7). - AgentManifest : étendre pour porter la liste
skills: Vec<SkillRef>assignés à chaque agent (0..N). - Use cases (
application/skill) :CreateSkill,UpdateSkill,DeleteSkill,ListSkills(scope),AssignSkillToAgent,UnassignSkillFromAgent. - Injection : à l'activation (fil L6), composer le convention file en concaténant persona agent + chemin project root + skills assignés (lus via
SkillStore). Pas de mécanisme CLI propriétaire. - Front : onglet/section Skills (liste globale + projet, CRUD, éditeur md), assignation skills↔agent dans
AgentsPanel.
Périmètre (TEST)
Skillrejettename/content_mdvides.SkillStore: CRUD round-trip en tmpdir pour les deux scopes ; un skillProjectn'apparaît pas dans le scopeGlobalet inversement.AssignSkillToAgent/UnassignSkillFromAgent: mutent l'AgentManifest, émettentSkillAssigned, idempotents (pas de doublon).- Injection : le convention file généré contient bien le
content_mddes skills assignés et rien des skills non assignés ; ordre déterministe. - Front : CRUD skills + assignation via gateway mock (RTL) ; garde-fou « no direct invoke ».
Definition of Done
cargo test(skill/store/app) +vitestverts ; cycle manuel : créer un skill, l'assigner à un agent, l'activer → le skill apparaît dans le convention file de.ideai/run/<agent-id>/.- DoD commune (cf. README) respectée ; zéro régression.
Avancement
✅ Domaine (vert)
- Entité
Skill(domain/skill.rs) :id: SkillId,name,content_md: MarkdownDoc,scope: SkillScope(Global|Project). Constructeur validant (name+content_mdnon vides),with_contentre-valide l'invariant. SkillRef { skill_id, scope }: référence d'assignation portée par l'agent ;From<&Skill>.SkillIdajouté (ids.rs), eventSkillAssigned { agent_id, skill_id, assigned }(events.rs), DTO + arm de mapping côtéapp-tauri(events.rs).Agentétendu : champskills: Vec<SkillRef>(serdedefault), méthodesassign_skill(idempotent),unassign_skill,with_skills(dédup).ManifestEntry: champskills(serdedefault+skip_serializing_if→ rétrocompat des manifests pré-L12) ;from_agent/to_agentpréservent les skills.- Tests : 8 invariants (
entities.rs) + 3 serde dont rétrocompat d'un manifest legacy sans cléskills(serde_roundtrip.rs).cargo test -p domainvert ;cargo test --workspacevert (0 régression) ; clippy clean.
✅ Port + adapter store (vert)
- Port
SkillStore(domain/ports.rs) :list/get/save/deleteportantscope+root: &ProjectPathpar appel (root ignoré pourGlobal, résolu pourProject) — un seul store sert tous les projets ouverts, commeAgentContextStore. - Adapter
FsSkillStore(infrastructure/store/skill.rs) : même forme on-disk queFsTemplateStore(index.json+md/<id>.md), deux racines disjointes :<app_data>/skills/(Global) et<root>/.ideai/skills/(Project). Delete laisse l'orphelin md (pas de remove dans le port FS), index = source de vérité. 7 tests d'intégration tmpdir (skill_store.rs) : round-trip 2 scopes, isolation de scope, upsert, delete idempotent, camelCase.
✅ Use cases application (vert)
application/skill:CreateSkill,UpdateSkill,DeleteSkill,ListSkills(scope)(inputs portantproject_root),AssignSkillToAgent/UnassignSkillFromAgent(mutent l'AgentManifestviato_agent/from_agent, dédup, émettentSkillAssigned, idempotents). 9 tests (skill_usecases.rs).
✅ Injection dans le convention file (vert, fil L6)
LaunchAgentreçoit le portSkillStore;resolve_skillslit les.mddes skills assignés (ordre manifest, déterministe ; skill supprimé =SkillRefpendant → ignoré sans bloquer le lancement).compose_convention_fileétendu : section# Skills(sous-titres## <name>) après le persona ; omise si aucun skill. 3 tests unitaires + e2e (agent_lifecycle.rs: injection ordonnée, ref pendant tolérée).- Composition root (
app-tauri/state.rs) :FsSkillStoreconstruit (app-data global), injecté dansLaunchAgent.
✅ IPC app-tauri (vert)
- DTOs (
dto.rs) :SkillDto(transparent surSkill, camelCase),SkillListDto, request DTOs (Create/Update/Assign/UnassignSkillRequestDto),parse_skill_id.scopedésérialise directement versSkillScope("global"/"project"). - Commandes (
commands.rs) :create_skill,update_skill,list_skills,delete_skill,assign_skill_to_agent,unassign_skill_from_agent— shells fins qui résolvent leProject(→project.root) puis appellent le use case. Enregistrées danslib.rs. - Composition root (
state.rs) : 6 use cases skill câblés sur leskill_store_port(déjà construit pour le launcher) et lecontexts_portpartagé. cargo build -p app-tauri+cargo test --workspace(304) verts ; clippy clean.
✅ Front features/skills (vert)
- Domaine (
domain/index.ts) :SkillScope,Skill,SkillRef;Agentétendu avecskills: SkillRef[]. - Port (
ports/index.ts) :SkillGateway(list/create/update/delete + assign/unassign) +CreateSkillInput; ajouté àGateways. - Adapters :
TauriSkillGateway(adapters/skill.ts, invoke camelCase) ;MockSkillGateway(adapters/mock, scopes disjoints + mutation partagée duMockAgentGatewayvia_setSkills, assign idempotent). - Feature :
useSkills(VM 2 scopes),SkillEditor(overlay md edit/preview + sélecteur de scope),SkillsPanel(listes Project/Global, CRUD). Onglet Skills ajouté dansProjectsView. - Assignation dans
AgentsPanel: chips des skills assignés + sélecteur d'assignation + unassign, sur l'agent sélectionné ; refresh après mutation. - Tests (
skills.test.tsx, RTL viaDIProvider+ mocks) : CRUD project/global, isolation de scope, édition, suppression, assign/unassign reflétés sur l'agent, idempotence, garde-fou « no direct invoke » (aucune action run/launch).vitest: 229 verts (0 régression ; test « ten gateways » mis à jour).
⏳ Reste à faire
- Cycle manuel : créer un skill, l'assigner à un agent, l'activer → vérifier qu'il apparaît dans le convention file de
.ideai/run/<agent-id>/(à faire sur l'AppImage).