diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..419ad76 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,21 @@ +# PromptKey — CI Gitea Actions +# Sur chaque push : présence des fichiers attendus et cohérence du pack +# de démarrage (20 prompts, en-têtes avec id + title, variables déclarées). +# Le contrôle lui-même vit dans scripts/check_pack.py (exécutable aussi +# en local : python3 scripts/check_pack.py). + +name: ci + +on: + push: + pull_request: + +jobs: + verifier-pack: + runs-on: ubuntu-latest + steps: + - name: Récupérer le dépôt + uses: actions/checkout@v4 + + - name: Vérifier les fichiers et le pack de prompts + run: python3 scripts/check_pack.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f458e5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# PromptKey — fichiers locaux, jamais versionnés + +# Données d'usage et caches (régénérés automatiquement) +usage.json +index-cache.json +**/usage.json +**/index-cache.json + +# Journal d'exécution +promptkey.log +*.log + +# Paramètres locaux de la machine +config.ini + +# Binaires compilés et livrables +*.exe +dist/ + +# Bibliothèque locale de test (les vrais prompts vivent dans leur propre dossier/dépôt) +Bibliotheque-test/ +Bibliothèque-test/ +bibliotheque-test/ + +# Sauvegardes créées par l'Éditeur +*.bak + +# Divers +Thumbs.db +desktop.ini diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..729d019 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,22 @@ +# Journal des changements — PromptKey + +Le format suit [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) et le projet utilise le versionnage sémantique. + +## [1.0.0] — 2026-10-11 + +Première version. + +### Ajouté + +- Application tray AutoHotkey v2 : instance unique, aucun appel réseau, démarrage avec Windows sur demande. +- Quick Picker (`Ctrl+Alt+Espace`) : recherche floue instantanée insensible aux accents, aperçu du prompt, navigation entièrement au clavier, retour du focus à l'application d'origine. +- Bibliothèque de prompts en fichiers Markdown : un prompt = un fichier `.md`, catégorie = dossier de premier niveau, en-tête structuré (id, title, tags, targets, favorite, épinglé, raccourci propre, hotstring, variables). +- Gestionnaire et Éditeur intégrés : créer, modifier, dupliquer, supprimer (corbeille Windows), créer un prompt depuis la sélection ou le presse-papiers, validation des variables en direct. +- Moteur de variables : types `text`, `multiline`, `enum`, `number`, `date`, `bool`, `password` ; variables automatiques `{{selection}}`, `{{clipboard}}`, `{{date}}`, `{{datetime}}`, `{{time}}`, `{{app}}`, `{{cursor}}` ; mémorisation des derniers choix par prompt (sauf `password`). +- Moteur d'injection : presse-papiers sauvegardé puis restauré (méthode par défaut), frappe simulée (`SendText`) en repli, aucun envoi automatique par défaut. +- Profils de destination par IA (Claude, ChatGPT, Gemini, Qwen, DeepSeek, terminal, courriel…) : préambule, méthode d'injection, délais, option « envoyer après insertion » gardée et désactivée par défaut. +- Raccourcis globaux configurables, épinglés `Ctrl+Alt+1…9`, hotstrings (`::resu;`…), rapport de conflits de raccourcis. +- Historique d'usage local (`usage.json`) alimentant les listes Épinglés / Récents / Fréquents du Picker. +- Import/export de prompts (Markdown, dossiers, zip, JSON d'échange) et mode portable intégral. +- Pack de démarrage « essentiels » : 20 prompts en français, 4 catégories (Général, Écriture, Code, Analyse), dans `packs/essentiels/`. +- Compilation reproductible par `build.ps1` (tests, Ahk2Exe, archive portable, sommes SHA256) et CI Gitea Actions vérifiant la cohérence du pack (`scripts/check_pack.py`). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ada1342 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Bruno + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6c266b --- /dev/null +++ b/README.md @@ -0,0 +1,115 @@ +# PromptKey + +**Gestionnaire de prompts pour intelligences artificielles, sous Windows, écrit en AutoHotkey v2.** + +PromptKey vit dans la zone de notification et insère, en quelques frappes, le bon prompt dans n'importe quelle application — ChatGPT, Claude, Gemini, Qwen, DeepSeek, un terminal, un éditeur, un courriel. Une seule bibliothèque de prompts, pour toutes les IA. + +## Le geste central + +``` +Ctrl+Alt+Espace → taper « résu… » → Entrée → remplir 1-2 variables si demandé → le prompt est collé +``` + +Le texte sélectionné dans l'application d'origine est capturé automatiquement (`{{selection}}`) ; PromptKey rend ensuite le focus à l'application et colle le prompt résolu. Il **insère** le texte — il ne l'envoie jamais à votre place (sauf option explicite par profil, désactivée par défaut). + +## Installation + +Prérequis : + +- Windows 10 ou 11 +- [AutoHotkey v2.0](https://www.autohotkey.com/) (64 bits) — seulement pour lancer la version source ou recompiler + +Deux façons de lancer : + +1. **Version compilée** : `PromptKey.exe` (voir « Compilation » ci-dessous ou l'archive portable `dist\PromptKey-portable.zip`). Aucune installation : double-cliquer, l'icône apparaît dans la zone de notification. +2. **Version source** : double-cliquer sur `PromptKey.ahk`, ou `AutoHotkey64.exe PromptKey.ahk` en ligne de commande. + +Premier lancement : le dossier de bibliothèque est créé automatiquement (`Documents\PromptKey\Bibliothèque`, ou le dossier `Bibliothèque\` à côté du programme en mode portable) et, s'il est vide, le **pack de démarrage** (`packs\essentiels\`, 20 prompts prêts à l'emploi en 4 catégories : Général, Écriture, Code, Analyse) y est recopié. Le dossier peut ensuite être changé dans les Paramètres. + +Pour démarrer PromptKey avec Windows : créer un raccourci vers `PromptKey.ahk` (ou l'exécutable compilé) dans le dossier Démarrage (`shell:startup`). + +## Raccourcis par défaut + +| Raccourci | Action | +|-----------|--------| +| `Ctrl+Alt+Espace` | Ouvrir le Quick Picker (rechercher et insérer un prompt) | +| `Ctrl+Alt+M` | Ouvrir le Gestionnaire (bibliothèque complète) | +| `Ctrl+Alt+N` | Nouveau prompt (depuis la sélection, s'il y en a une) | +| `Ctrl+Alt+1` … `Ctrl+Alt+9` | Insérer directement le prompt épinglé n° 1…9 | +| `Ctrl+Alt+V` | Réinsérer le dernier prompt utilisé | + +Dans le Picker : `↑`/`↓` naviguer, `Entrée` utiliser, `Ctrl+Entrée` utiliser avec les valeurs par défaut, `Ctrl+E` modifier, `Échap` fermer sans rien insérer. + +Chaque prompt peut aussi avoir son propre raccourci (`hotkey:`) ou une abréviation tapée (`hotstring:`, ex. taper `resu;` insère le prompt « Résumer en 5 points »). Tous les raccourcis sont modifiables dans les Paramètres. + +## Format d'un prompt + +**Un prompt = un fichier Markdown.** La catégorie est le dossier qui le contient ; l'en-tête entre `---` décrit le prompt, le corps est le texte du prompt avec ses variables `{{…}}` : + +```markdown +--- +id: 96d0d5b7-7a07-43a1-af84-dfa7024b4bcf +title: Résumer en 5 points +tags: [résumé, synthèse, court] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: true +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: ton + label: Ton de la réponse + type: enum + choices: [neutre, formel, amical, direct] + default: neutre +--- + +Résume le texte suivant, ton {{ton}}. + +Texte : +{{selection}} +``` + +Variables automatiques (jamais demandées) : `{{selection}}` (texte sélectionné), `{{clipboard}}` (presse-papiers), `{{date}}`, `{{datetime}}`, `{{time}}`, `{{app}}` (application d'origine), `{{cursor}}` (position du curseur après insertion). + +Le format complet (sous-ensemble d'en-tête accepté, profils de destination, moteur d'injection) est spécifié dans le document d'architecture et de conception de PromptKey, §7 et suivants. + +## Structure du projet + +``` +PromptKey\ +├─ PromptKey.ahk point d'entrée (tray, démarrage) +├─ lib\ services : magasin de prompts, index/recherche, +│ moteur de variables, injecteur, presse-papiers… +├─ gui\ Quick Picker, Gestionnaire, Éditeur, formulaires +├─ packs\essentiels\ pack de démarrage : 20 prompts, 4 catégories +├─ scripts\check_pack.py contrôle de cohérence du pack (utilisé par la CI) +├─ tests\ suites de tests AutoHotkey +├─ build.ps1 compilation + archive portable (voir ci-dessous) +├─ config.ini paramètres (créé au premier lancement, local) +└─ README.md +``` + +La bibliothèque de prompts peut vivre ailleurs (dossier au choix dans les Paramètres) ; elle est faite de simples fichiers `.md`, versionnables sous Git — ce dépôt en est la démonstration. + +## Compilation + +Avec PowerShell, à la racine du projet : + +```powershell +.\build.ps1 +``` + +Le script vérifie la présence d'AutoHotkey v2 et d'**Ahk2Exe**, lance les tests de `tests\` s'ils sont présents, compile `PromptKey.ahk` en `dist\PromptKey.exe`, puis assemble l'archive portable `dist\PromptKey-portable.zip` (exe + packs + README + LICENSE). + +Ahk2Exe est fourni avec AutoHotkey v2 (dossier `Compiler\`) ou via le programme d'installation d'AutoHotkey (option « Compile »). + +## Sécurité et vie privée + +- **Zéro réseau.** PromptKey n'appelle aucun service, n'envoie aucune télémétrie, ne parle à aucune API d'IA : il prépare du texte et le colle localement. +- **Presse-papiers restauré.** L'injection par presse-papiers sauvegarde son contenu, colle le prompt, puis restaure l'original après un court délai (abandon propre si vous avez copié autre chose entre-temps). Les prompts marqués `sensitive: true` vident le presse-papiers après injection au lieu de le restaurer. +- **Aucun contenu journalisé.** Le journal ne contient ni prompts, ni sélections, ni valeurs de variables ; les variables de type `password` ne sont jamais persistées. +- **Ne mettez pas de secrets dans vos prompts** : ce sont des fichiers texte en clair, pensés pour être versionnés. + +## Licence + +MIT — voir [LICENSE](LICENSE). Copyright © 2026 Bruno. diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..6021ba0 --- /dev/null +++ b/build.ps1 @@ -0,0 +1,85 @@ +# PromptKey — compilation et assemblage de l'archive portable. +# +# Usage : .\build.ps1 +# +# Étapes : +# 1. Vérifie la présence d'AutoHotkey v2 et du compilateur Ahk2Exe. +# 2. Lance les scripts de tests\ s'il y en a (échec = arrêt du build). +# 3. Compile PromptKey.ahk en dist\PromptKey.exe. +# 4. Assemble dist\PromptKey-portable.zip (exe + packs + README + LICENSE). +# 5. Affiche la somme SHA256 de l'exécutable. +# +# Prérequis : AutoHotkey v2.0 (64 bits) installé, Ahk2Exe fourni avec +# (dossier Compiler\ de l'installation AutoHotkey). + +#Requires -Version 5.1 +$ErrorActionPreference = 'Stop' +$Racine = Split-Path -Parent $MyInvocation.MyCommand.Path +Set-Location $Racine + +function Echouer($Message) { + Write-Host "ERREUR : $Message" -ForegroundColor Red + exit 1 +} + +# --- 1. Localiser AutoHotkey v2 et Ahk2Exe ---------------------------------- +$AhkExe = $null +$Ahk2Exe = $null +$Candidats = @( + "$env:ProgramFiles\AutoHotkey", + "${env:ProgramFiles(x86)}\AutoHotkey", + "$env:LOCALAPPDATA\Programs\AutoHotkey" +) +foreach ($Dossier in $Candidats) { + if (Test-Path $Dossier) { + $Trouve = Get-ChildItem -Path $Dossier -Recurse -Filter 'AutoHotkey64.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($Trouve) { $AhkExe = $Trouve.FullName } + $Trouve = Get-ChildItem -Path $Dossier -Recurse -Filter 'Ahk2Exe.exe' -ErrorAction SilentlyContinue | Select-Object -First 1 + if ($Trouve) { $Ahk2Exe = $Trouve.FullName } + } +} +if (-not $AhkExe) { Echouer "AutoHotkey v2 introuvable. Installez AutoHotkey v2.0 (64 bits) depuis https://www.autohotkey.com/" } +if (-not $Ahk2Exe) { Echouer "Ahk2Exe introuvable. Il est fourni avec AutoHotkey v2 (dossier Compiler\) ; réinstallez en incluant l'option de compilation." } + +# Vérifier qu'il s'agit bien de la v2 (le champ FileVersion commence par 2.) +$Version = (Get-Item $AhkExe).VersionInfo.FileVersion +if ($Version -notmatch '^2\.') { Echouer "AutoHotkey v2 requis, version trouvée : $Version ($AhkExe)" } +Write-Host "AutoHotkey $Version : $AhkExe" +Write-Host "Ahk2Exe : $Ahk2Exe" + +# --- 2. Tests (si présents) -------------------------------------------------- +$DossierTests = Join-Path $Racine 'tests' +if (Test-Path $DossierTests) { + $Scripts = Get-ChildItem -Path $DossierTests -Filter '*.ahk' + foreach ($Script in $Scripts) { + Write-Host "Test : $($Script.Name)" + & $AhkExe /ErrorStdOut $Script.FullName + if ($LASTEXITCODE -ne 0) { Echouer "Échec du test $($Script.Name) (code $LASTEXITCODE). Build interrompu." } + } + if ($Scripts.Count -eq 0) { Write-Host "Aucun script de test dans tests\ — étape sautée." } +} + +# --- 3. Compilation ---------------------------------------------------------- +$Dist = Join-Path $Racine 'dist' +if (-not (Test-Path $Dist)) { New-Item -ItemType Directory -Path $Dist | Out-Null } +$Source = Join-Path $Racine 'PromptKey.ahk' +if (-not (Test-Path $Source)) { Echouer "Fichier source introuvable : $Source" } +$Exe = Join-Path $Dist 'PromptKey.exe' +if (Test-Path $Exe) { Remove-Item $Exe -Force } + +Write-Host "Compilation de PromptKey.ahk ..." +& $Ahk2Exe /in $Source /out $Exe /base $AhkExe +if ($LASTEXITCODE -ne 0 -or -not (Test-Path $Exe)) { Echouer "La compilation a échoué." } +Write-Host "Exécutable produit : $Exe" + +# --- 4. Archive portable ----------------------------------------------------- +$Zip = Join-Path $Dist 'PromptKey-portable.zip' +if (Test-Path $Zip) { Remove-Item $Zip -Force } +$AInclure = @($Exe, (Join-Path $Racine 'README.md'), (Join-Path $Racine 'LICENSE'), (Join-Path $Racine 'CHANGELOG.md'), (Join-Path $Racine 'packs')) +Compress-Archive -Path $AInclure -DestinationPath $Zip +Write-Host "Archive portable : $Zip" + +# --- 5. Somme de contrôle ---------------------------------------------------- +$Hash = Get-FileHash -Algorithm SHA256 $Exe +Write-Host "SHA256 de l'exe : $($Hash.Hash)" +Write-Host "Build terminé." diff --git a/docs/architecture-prompt-manager-autohotkey.md b/docs/architecture-prompt-manager-autohotkey.md new file mode 100644 index 0000000..6e1b1c3 --- /dev/null +++ b/docs/architecture-prompt-manager-autohotkey.md @@ -0,0 +1,1030 @@ +# PromptKey — Gestionnaire de prompts pour IA sous AutoHotkey + +**Architecture et conception — v1.0 — 2026-10-11** +**Statut :** document de conception, à valider avant implémentation +**Nom de travail :** PromptKey (à confirmer, §20-Q1) +**Environnement cible :** Windows 10 / 11, AutoHotkey v2.0 (64 bits) + +--- + +## Table des matières + +1. Résumé décisionnel +2. Le problème et les cas d'usage +3. Périmètre : ce que PromptKey fait, et ce qu'il ne fait pas +4. Pourquoi AutoHotkey — et ses limites assumées +5. Décisions structurantes (D1 à D8) +6. Architecture d'ensemble +7. Modèle de données : un prompt = un fichier Markdown +8. Le Quick Picker : la surface principale +9. Le Gestionnaire et l'Éditeur +10. Le moteur de variables +11. Le moteur d'injection : livrer le prompt à l'IA +12. Profils de destination (les « IA cibles ») +13. Raccourcis globaux et hotstrings +14. Recherche : index en mémoire et classement flou +15. Historique, favoris et statistiques d'usage +16. Import, export et packs de prompts +17. Paramètres, portabilité et synchronisation +18. Sécurité et vie privée +19. Architecture du code AutoHotkey v2 +20. Maquettes des écrans (ASCII) +21. Packaging, installation et mises à jour +22. Tests et critères de qualité +23. Plan d'implémentation en phases (tickets) +24. Risques et mitigations +25. Alternatives écartées +26. Décisions ouvertes pour Bruno +- Annexe A — Exemple complet d'un fichier prompt +- Annexe B — Algorithme de classement de la recherche +- Annexe C — Séquence d'injection, pas à pas +- Annexe D — Pack de démarrage proposé (20 prompts) + +--- + +## 1. Résumé décisionnel + +PromptKey est une petite application Windows, écrite en **AutoHotkey v2**, qui vit dans la zone de notification (tray) et qui répond à un seul besoin, partout dans Windows, en moins de deux secondes : + +> « Je suis dans ChatGPT / Claude / Gemini / Qwen / un éditeur / un courriel. Je veux insérer **le bon prompt**, adapté à **ce texte-ci**, sans quitter le clavier ni fouiller dans un fichier de notes. » + +Le geste central tient en trois touches et quelques frappes : + +``` +Ctrl+Alt+Espace → taper « résu… » → Entrée → remplir 1-2 variables → le prompt est collé, prêt à envoyer +``` + +Les cinq choix qui structurent tout le reste du document : + +| # | Décision | En une phrase | +|---|----------|---------------| +| D1 | **AutoHotkey v2 uniquement** | Pas de v1 : syntaxe moderne, classes, `Map`, meilleure maintenabilité. | +| D2 | **Un prompt = un fichier `.md`** | Source de vérité en fichiers Markdown avec en-tête structuré : lisibles, éditables partout (dont Obsidian), versionnables sous Git/Gitea, jamais enfermés dans un format propriétaire. | +| D3 | **Index en mémoire, pas de base de données en v1** | Chargement complet au démarrage + cache JSON ; suffisant et rapide jusqu'à ~5 000 prompts, zéro DLL externe. | +| D4 | **Injection par presse-papiers restauré, par défaut** | Coller via `Ctrl+V` après sauvegarde du presse-papiers, restauration après délai ; `SendText` en repli configurable pour les champs récalcitrants. | +| D5 | **Aucun appel réseau, aucun envoi automatique** | PromptKey **insère** du texte dans le champ actif ; il n'appelle aucune API d'IA et n'appuie jamais sur « Envoyer » à la place de l'utilisateur (option explicite par profil, désactivée par défaut). | + +Conséquence directe : PromptKey est **agnostique de l'IA**. Il sert aussi bien une page web ChatGPT qu'une application de bureau, un terminal, Hermes, un client courriel ou un document Word — partout où un champ texte accepte un collage. Changer d'IA ne change rien à la bibliothèque de prompts ; seuls des **profils de destination** (§12) ajustent la forme (longueur, format, langue, touche d'envoi). + +Livrable visé à la fin du plan (§23) : un `PromptKey.exe` portable compilé (Ahk2Exe), un dossier `Prompts\` de départ (Annexe D), et ce document comme cahier des charges. + +--- + +## 2. Le problème et les cas d'usage + +### 2.1 Le problème, concrètement + +Travailler avec plusieurs IA au quotidien produit le même cycle coûteux : + +1. **Dispersion.** Les bons prompts vivent dans des notes, des courriels, des fichiers texte, la mémoire — rarement retrouvés au moment voulu. +2. **Retapage.** Le même prompt « résume ce texte en 5 points, ton neutre » est retapé, réadapté, dégradé à chaque usage. +3. **Perte du contexte.** Le prompt idéal dépend du texte sélectionné, du presse-papiers, de la date, du projet — recopiés à la main. +4. **Friction de changement d'outil.** Un prompt taillé pour une IA (format, longueur, consignes) est réécrit pour l'autre. +5. **Aucune mémoire d'usage.** Impossible de savoir quels prompts servent vraiment, lesquels sont des doublons morts. + +Le coût n'est pas le temps de frappe : c'est l'**interruption de flux** — quitter l'application, chercher, copier, revenir, recoller, réadapter. + +### 2.2 Cas d'usage nominaux + +| # | Cas | Déroulement cible | +|---|-----|-------------------| +| U1 | **Prompt simple** | Dans Claude (web), `Ctrl+Alt+Espace` → « traduis » → Entrée → le prompt de traduction est collé avec le texte sélectionné déjà incorporé. | +| U2 | **Prompt à variables** | « Revue de code » demande `{{langage}}` (liste : Python / JS / AHK…) et `{{niveau}}` (liste : rapide / strict) dans un mini-formulaire, puis colle le prompt résolu. | +| U3 | **Prompt sur sélection** | Sélectionner un paragraphe dans Word → raccourci → choisir « Reformuler » → le texte sélectionné est capturé automatiquement et injecté dans `{{selection}}`. | +| U4 | **Prompt favori sans recherche** | `Ctrl+Alt+1` colle directement le prompt épinglé n° 1, sans ouvrir aucune fenêtre. | +| U5 | **Abréviation tapée** | Taper `::resu;` dans n'importe quel champ le remplace par le prompt « Résumer » (hotstring, §13.2). | +| U6 | **Bibliothèque qui grandit** | Nouveau prompt créé depuis le Gestionnaire en 30 s, ou **depuis la sélection** (« Enregistrer la sélection comme prompt »), classé par catégorie et étiquettes. | +| U7 | **Changement d'IA** | Le même prompt « Explique » est utilisé dans Qwen avec le profil Qwen (consignes resserrées, réponse en français) et dans ChatGPT avec le profil par défaut — le corps du prompt ne change pas, le profil ajoute son préambule. | +| U8 | **Partage / sauvegarde** | Exporter une catégorie en un fichier `.zip` de `.md` (ou la versionner dans un dépôt Gitea) ; l'importer sur un autre poste en glisser-déposer. | + +### 2.3 Personnes et contexte d'usage + +Utilisateur unique, poste Windows personnel, usage au clavier d'abord. L'application doit rester utile **hors ligne**, sans compte, sans serveur — y compris quand l'IA cible est elle-même locale ou dans un terminal. + +--- + +## 3. Périmètre : ce que PromptKey fait, et ce qu'il ne fait pas + +### 3.1 Dans le périmètre (v1 visée par ce document) + +- Bibliothèque locale de prompts : créer, modifier, dupliquer, supprimer, classer (catégories + étiquettes), épingler. +- Recherche instantanée floue (titre, étiquettes, catégorie, corps) et navigation 100 % clavier. +- Variables typées dans les prompts, résolues par mini-formulaire ou automatiquement (sélection, presse-papiers, date…). +- Injection du prompt résolu dans **l'application active**, par presse-papiers ou par frappe simulée. +- Profils de destination (préambule, format, option « envoyer après insertion » désactivée par défaut). +- Raccourcis globaux personnalisés par prompt + hotstrings. +- Historique d'usage local, favoris, compteurs (pour le classement « fréquents »). +- Import/export Markdown et JSON ; packs de prompts. +- Paramètres : raccourci principal, délais d'injection, thème, dossier de bibliothèque, démarrage avec Windows. + +### 3.2 Hors périmètre (explicitement) + +- **Appeler une API d'IA.** PromptKey ne parle à aucun modèle ; il prépare et insère du texte. (Une extension « envoyer à l'API et coller la réponse » est discutée en phase optionnelle §23.7, hors v1.) +- **Gérer des conversations** ou l'historique des réponses des IA. +- **Synchroniser tout seul** : la synchro passe par le dossier (Git, OneDrive, dossier partagé) — PromptKey détecte les changements de fichiers, il ne transporte rien (§17.3). +- **Mac / Linux** : AutoHotkey est Windows seulement (§4.2). +- **Multi-utilisateur, partage en temps réel, permissions.** +- **Stocker des secrets** : les prompts ne doivent pas contenir de clés d'API ; les variables de type « masqué » existent pour la saisie ponctuelle, jamais persistées (§18). + +--- + +## 4. Pourquoi AutoHotkey — et ses limites assumées + +### 4.1 Ce qu'AutoHotkey v2 apporte, exactement pour ce besoin + +- **Raccourcis globaux et hotstrings natifs**, fiables dans presque toutes les applications Windows — c'est le cœur du besoin, et c'est la spécialité historique d'AHK. +- **Manipulation du presse-papiers** (`A_Clipboard`, `ClipWait`) et **envoi de frappes** (`Send`, `SendText`) intégrés, sans dépendance. +- **GUI native** (`Gui`, `ListView`, `TreeView`, `Edit`) suffisante pour un sélecteur rapide et un gestionnaire en trois volets. +- **Exécutable autonome** via Ahk2Exe : un seul `.exe`, aucun runtime à installer chez l'utilisateur final (AHK v2 reste requis seulement pour développer). +- **Démarrage rapide, empreinte mémoire faible**, processus tray discret — adapté à un outil invoqué 50 fois par jour. + +### 4.2 Limites assumées (et leur traitement dans ce document) + +| Limite | Traitement | +|--------|-----------| +| Windows seulement | Assumé (§3.2). La bibliothèque en `.md` reste portable : si un équivalent Mac/Linux naît un jour, il relira les mêmes fichiers. | +| Applications **élevées** (administrateur) : un AHK non élevé ne peut pas leur envoyer de frappes | Détection + message clair (§11.5) ; option « relancer en administrateur » documentée, jamais forcée. | +| Champs riches (éditeurs web, contenteditable) où le collage formate ou échoue | Profils par application avec méthode d'injection dédiée (§12) ; repli `SendText`. | +| Les `.exe` compilés AHK déclenchent parfois des **faux positifs antivirus** | Distribution du `.ahk` source en parallèle, compilation reproductible documentée (§21.3) ; pas de comportement réseau qui aggraverait le score. | +| GUI native limitée (pas de « beau » framework) | Design fonctionnel : Picker minimal et rapide plutôt que joli ; soin porté à la typographie, au thème sombre et au clavier (§20). | +| Pas de vrai YAML ni de SQLite intégrés | Sous-ensemble d'en-tête strictement défini et parsé par un module dédié (§7.3) ; index en mémoire (§14). | +| Raccourcis déjà pris par d'autres applications | Détection de conflit à l'enregistrement et rapport dans les Paramètres (§13.3). | + +--- + +## 5. Décisions structurantes (D1 à D8) + +### D1 — AutoHotkey v2.0 64 bits uniquement + +Toute la base de code cible la syntaxe v2 : classes, `Map()`, fonctions fléchées, `Gui` orienté objet. Aucun code v1, aucune double compatibilité : mélanger les dialectes est la première source de bogues AHK. Version minimale épinglée : **v2.0.10+** (correctifs `ListView` / DPI). + +### D2 — Un prompt = un fichier Markdown + +Chaque prompt est un fichier `Nom-du-prompt.md` dans le dossier de bibliothèque : + +- **En-tête** (frontmatter délimité par `---`) : métadonnées structurées (§7.2). +- **Corps** : le texte du prompt, avec ses variables `{{…}}`. + +Avantages décisifs pour l'usage visé : éditable dans n'importe quel éditeur (dont le Gestionnaire intégré, mais aussi VS Code ou Obsidian), cherchable par `grep`, versionnable sous Git (un dépôt Gitea privé fait une sauvegarde et un historique parfaits), partageable fichier par fichier. Le coût — un parseur d'en-tête maison — est borné par un sous-ensemble strict (§7.3) et couvert par des tests (§22). + +*Rejeté :* base SQLite comme source de vérité (opaque, non éditable à la main, DLL externe) ; fichier JSON unique géant (conflits de fusion, illisible dès 100 prompts). Le JSON reste utilisé pour le **cache d'index** (§14.2) et l'**export**, jamais comme source. + +### D3 — Index en mémoire reconstruisible + +Au démarrage, PromptKey lit tous les `.md`, construit l'index en mémoire et le met en cache (`index-cache.json`, invalidé par date/taille des fichiers). La recherche travaille sur l'index, pas sur le disque. Plafond de conception : **5 000 prompts, recherche < 100 ms**. Au-delà, une phase ultérieure pourra adosser SQLite sans changer le format source (D2 reste intact). + +### D4 — Injection : presse-papiers restauré par défaut, frappe en repli + +Comparatif tranché (§11 donne le détail) : + +| Méthode | Vitesse | Fidélité (Unicode, sauts de ligne) | Risques | +|---------|---------|-------------------------------------|---------| +| Presse-papiers + `Ctrl+V` | Instantanée | Excellente | Écrase brièvement le presse-papiers → **sauvegarde/restauration obligatoires** | +| `SendText` (frappe simulée) | Lente sur longs prompts | Bonne, mais sensible à la disposition clavier | Interférences (IMEs, champs qui interceptent), focus perdu = texte dans le vide | + +Défaut : presse-papiers, avec restauration après un délai configurable (1,5 s par défaut) et **abandon de la restauration si l'utilisateur a copié autre chose entre-temps** (§11.3). + +### D5 — Insérer, ne pas envoyer + +Après injection, le curseur reste chez l'utilisateur : il relit, ajuste, envoie lui-même. L'option « Entrée automatique après insertion » existe **par profil de destination**, désactivée par défaut, avec un délai de grâce de 800 ms annulable par `Échap` (§12.3). Principe : un mauvais prompt collé se corrige ; un mauvais prompt **envoyé** — éventuellement au mauvais fil — ne se rattrape pas. + +### D6 — Processus tray unique, aucune fenêtre au repos + +Instance unique (`#SingleInstance Force`), icône tray, aucune fenêtre visible tant qu'on n'invoque pas PromptKey. Fermer une fenêtre = la masquer, jamais quitter (quitter se fait par le menu tray). + +### D7 — Zéro réseau en v1 + +Aucun appel sortant : ni télémétrie, ni vérification de mise à jour en ligne, ni « amélioration IA du prompt ». Cela simplifie la sécurité (§18), les antivirus (§4.2) et la confiance. Les mises à jour sont manuelles (§21.4). + +### D8 — Clavier d'abord, souris en appoint + +Chaque action du Picker et du Gestionnaire a un chemin clavier complet ; les listes se naviguent aux flèches, `Entrée` valide, `Échap` ferme et **rend le focus à l'application d'origine** (§11.2 — le retour de focus est une fonction de premier rang, pas un détail). + +--- + +## 6. Architecture d'ensemble + +### 6.1 Vue logique + +``` +┌────────────────────────────────────────────────────────────────────┐ +│ PromptKey.exe (AHK v2) │ +│ │ +│ ┌──────────────┐ ┌───────────────┐ ┌───────────────────────┐ │ +│ │ Entrées │ │ Surfaces GUI │ │ Services (classes) │ │ +│ │ │ │ │ │ │ │ +│ │ Raccourcis │──▶│ Quick Picker │──▶│ PromptStore (fichiers)│ │ +│ │ globaux │ │ Gestionnaire │ │ Index/Recherche │ │ +│ │ Hotstrings │ │ Éditeur │ │ VariableEngine │ │ +│ │ Menu tray │ │ Formulaire │ │ Injector │ │ +│ │ Clic tray │ │ variables │ │ ClipboardGuard │ │ +│ │ │ │ Paramètres │ │ HotkeyManager │ │ +│ └──────────────┘ └───────────────┘ │ UsageStats │ │ +│ ▲ │ Settings │ │ +│ │ └──────────┬────────────┘ │ +└─────────┼──────────────────────────────────────────┼───────────────┘ + │ │ + Clavier/souris Disque (local uniquement) + (toutes applications) ┌───────────────────────────────┐ + │ Bibliothèque\ *.md (source) │ + │ config.ini / index-cache.json │ + │ usage.json / promptkey.log│ + └───────────────────────────────┘ +``` + +### 6.2 Flux principal (le « chemin heureux ») + +``` +1. L'utilisateur travaille dans l'application X (focus sur X). +2. Raccourci global → PromptKey mémorise la fenêtre active (hwnd X) et son titre. +3. Le Picker s'ouvre (déjà chargé : l'index est en mémoire), focus sur la recherche. +4. Frappe + flèches → le prompt choisi ; aperçu du corps à droite. +5. Si le prompt a des variables manuelles → mini-formulaire (les variables + automatiques — sélection, presse-papiers — sont déjà capturées, §10.3). +6. VariableEngine produit le texte final (corps + préambule du profil actif). +7. Le Picker se ferme, le focus revient sur X, Injector colle le texte. +8. UsageStats enregistre l'usage (id, horodatage) — jamais le contenu résolu (§18). +``` + +Étape critique : **2 → 7, la capture et la restitution du focus**. Si le focus ne revient pas exactement dans le champ d'origine, l'injection part dans le vide. Le module `Injector` (§11) est donc isolé, testé à part, et toutes les surfaces passent par lui — aucun `Send` dispersé dans les GUI. + +### 6.3 Démarrage et cycle de vie + +- Lancement (manuel ou démarrage Windows) → chargement des paramètres → scan de la bibliothèque → construction/vérification du cache d'index → enregistrement des raccourcis globaux et hotstrings → icône tray → attente. +- Un **surveillant de dossier** (sondage léger toutes les 5 s sur les dates de modification, ou `WM_DEVICECHANGE`-like par minuteur) détecte les `.md` ajoutés/modifiés/supprimés hors de l'application (édition dans VS Code, `git pull`…) et rafraîchit l'index sans redémarrage. +- Arrêt propre par le menu tray : libération des raccourcis, vidage du cache vers le disque, restauration du presse-papiers si une restauration était en attente. + +--- + +## 7. Modèle de données : un prompt = un fichier Markdown + +### 7.1 Arborescence de la bibliothèque + +``` +Bibliothèque\ +├─ _packs\ ← packs importés, non édités directement (optionnel) +├─ Code\ +│ ├─ revue-de-code.md +│ └─ expliquer-une-erreur.md +├─ Écriture\ +│ ├─ reformuler.md +│ └─ resumer-en-5-points.md +├─ Général\ +│ └─ expliquer-simplement.md +└─ Sans catégorie\ ← dossier réel ou implicite (fichiers à la racine) +``` + +**La catégorie = le dossier de premier niveau.** Choix délibéré : la structure est visible et manipulable dans l'Explorateur Windows, aucun champ à maintenir en double. Sous-dossiers au-delà du premier niveau : ignorés en v1 (organisation plate par étiquettes pour le reste). Renommer un dossier = renommer la catégorie, détecté par le surveillant (§6.3). + +### 7.2 L'en-tête du fichier (métadonnées) + +```markdown +--- +id: 7f3a2c1e-… # GUID stable, généré à la création, ne change jamais +title: Résumer en 5 points +tags: [résumé, synthèse, court] +targets: [défaut, claude, qwen] # profils de destination conseillés (§12) +favorite: true +pinned: 2 # rang dans les raccourcis directs (0 = non épinglé) +hotkey: ^!r # raccourci propre (syntaxe AHK), optionnel +hotstring: resu; # abréviation ::resu; , optionnelle +profile: défaut # profil appliqué si targets ne tranche pas +send_after: false # surcharge du profil (§12.3) +sensitive: false # si true : presse-papiers vidé après injection (§18, point 6) +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: ton + label: Ton de la réponse + type: enum + choices: [neutre, formel, amical, direct] + default: neutre + - name: longueur + label: Longueur max + type: enum + choices: ["5 points", "1 paragraphe", "détaillé"] + default: "5 points" +--- + +Résume le texte suivant en {{longueur}}, ton {{ton}}. +Réponds en français, sans reformuler la première phrase. + +Texte : +{{selection}} +``` + +Le **corps** commence après le second `---`. Tout ce qui suit est le prompt, verbatim, y compris ses propres `---` éventuels (le parseur ne coupe qu'aux deux premiers délimiteurs). + +### 7.3 Le sous-ensemble d'en-tête accepté (contrat strict) + +AHK n'a pas de parseur YAML : PromptKey définit donc un **sous-ensemble ligne par ligne**, documenté ici comme contrat — tout le reste est une erreur de fichier signalée, jamais devinée : + +- Clés scalaires : `clé: valeur` (chaîne, `true/false`, nombre, date `AAAA-MM-JJ`). +- Listes en ligne : `[a, b, c]` — virgules, éléments éventuellement entre guillemets doubles. +- Bloc `variables:` : liste d'objets indentés de 2 espaces, clés parmi `name, label, type, choices, default, required, multiline`. +- Commentaires `#` en fin de ligne, lignes vides ignorées. +- **Interdits** (erreur explicite avec n° de ligne) : ancres YAML, listes multi-lignes hors `variables`, tableaux imbriqués, valeurs multi-lignes `|`. + +Un fichier en erreur n'est **jamais** exclu silencieusement : il apparaît dans le Gestionnaire sous « Fichiers en erreur » avec le message et la ligne, et double-cliquer l'ouvre dans l'éditeur de texte par défaut. Un prompt sans en-tête du tout est accepté : titre = nom du fichier, corps = tout le fichier (import de notes existantes sans friction). + +### 7.4 Identité et renommage + +L'`id` (GUID dans l'en-tête) est l'identité ; le **nom de fichier est libre** (renommer le fichier ne casse ni les statistiques ni les raccourcis). L'historique et les compteurs (§15) sont indexés par `id`. À la première lecture d'un fichier sans `id`, PromptKey le génère et réécrit l'en-tête (avec sauvegarde `.bak` unique). + +### 7.5 Champs calculés (jamais stockés dans le fichier) + +`usage_count`, `last_used`, `score de récence` : dérivés de `usage.json` (§15), pas du fichier prompt — pour que versionner la bibliothèque sous Git ne produise pas un diff à chaque usage. + +--- + +## 8. Le Quick Picker : la surface principale + +Le Picker est l'écran que l'utilisateur verra 95 % du temps. Ses exigences sont des exigences de **vitesse perçue** : + +| Exigence | Cible | +|----------|-------| +| Raccourci → fenêtre visible et focus prêt | < 150 ms (fenêtre pré-créée, masquée/montée) | +| Frappe → liste filtrée | < 50 ms par touche (recherche sur index en mémoire) | +| Ouverture → prompt collé (cas sans variable) | ≤ 4 frappes + Entrée, par défaut | +| Abandon (`Échap`) | Focus rendu à l'application d'origine, **zéro effet de bord** | + +### 8.1 Composition (voir maquette §20.1) + +- **Champ de recherche** (focus initial, texte sélectionné au retour). +- **Liste de résultats** : titre, catégorie, étiquettes, marqueurs (★ favori, 📌 épinglé, raccourci propre). Tri par défaut : épinglés → favoris → fréquents récents → pertinence de recherche. +- **Panneau d'aperçu** : corps du prompt avec les variables surlignées, et — si une sélection a été capturée — son début, tronqué proprement. +- **Barre de statut** : profil de destination actif, méthode d'injection qui sera utilisée, nombre de résultats. + +### 8.2 Interactions clavier (contrat) + +| Touche | Effet | +|--------|-------| +| `↑` / `↓`, `Ctrl+N` / `Ctrl+P` | Naviguer | +| `Entrée` | Utiliser le prompt (formulaire de variables si requis, sinon injection directe) | +| `Ctrl+Entrée` | Utiliser **sans** formulaire : valeurs par défaut pour toutes les variables | +| `Ctrl+C` (sur un résultat) | Copier le prompt résolu dans le presse-papiers, sans injecter | +| `Ctrl+E` | Ouvrir le prompt dans l'Éditeur | +| `Alt+1…9` | Basculer le profil de destination pour cette injection seulement | +| `Échap` | Fermer, rendre le focus, ne rien faire d'autre | +| Frappe directe | Filtrer (la recherche est toujours active, pas de mode séparé) | + +### 8.3 Listes par défaut (recherche vide) + +À l'ouverture sans frappe : **Épinglés**, puis **Récents** (7 derniers jours, dédupliqués), puis **Fréquents**. C'est ce qui rend l'outil rapide pour les 5 prompts du quotidien : la plupart des usages ne nécessitent aucune recherche. + +--- + +## 9. Le Gestionnaire et l'Éditeur + +### 9.1 Gestionnaire (fenêtre principale, §20.2) + +Ouvert par le menu tray, par `Ctrl+Alt+M` ou depuis le Picker (`Ctrl+E` sur la bibliothèque). Trois volets : + +1. **Arbre** : catégories (dossiers), Favoris, Récents, Épinglés, Fichiers en erreur — avec compteurs. +2. **Liste** : prompts de la sélection, triables (titre, modifié, usage), recherche locale. +3. **Détail / aperçu** : métadonnées et corps, boutons Utiliser / Modifier / Dupliquer / Exporter / Supprimer. + +Actions de masse : déplacer vers une catégorie (glisser-déposer ou menu), ajouter/retirer une étiquette, exporter la sélection. Suppression = corbeille Windows (pas de suppression définitive directe) — un prompt est un texte précieux, souvent le fruit d'itérations. + +Création express, trois chemins : + +- **Nouveau** (formulaire vide + modèle de corps commenté) ; +- **Depuis la sélection** : le texte sélectionné dans l'application courante devient le corps (déjà vu en U6) ; +- **Depuis le presse-papiers** : idem, contenu courant du presse-papiers. + +### 9.2 Éditeur (§20.3) + +Éditeur intégré volontairement simple — champ texte multiligne à tabulations conservées, panneau de métadonnées à droite (titre, catégorie, étiquettes, profils, favori/épinglé, raccourci, hotstring), et trois aides qui le distinguent d'un bloc-notes : + +- **Insertion de variable** (`Ctrl+J` ou bouton) : insère `{{nom}}` et, si la variable n'existe pas encore dans l'en-tête, propose de la déclarer (type, libellé, défaut) — la déclaration et l'usage restent synchronisés. +- **Validation en direct** : variables utilisées non déclarées, variables déclarées inutilisées, en-tête en erreur (ligne fautive), raccourci en conflit — affichés sous l'éditeur, sauvegarde bloquée seulement pour les erreurs d'en-tête, jamais pour les avertissements. +- **Tester** (`Ctrl+T`) : ouvre le formulaire de variables puis montre le **prompt résolu final** (avec profil) dans une fenêtre de prévisualisation, sans rien injecter. + +Enregistrer écrit le fichier en **UTF-8 explicite** (jamais l'encodage ANSI par défaut d'AHK, voir le piège détaillé en §19.4) et rafraîchit l'index immédiatement. + +### 9.3 Ce que l'Éditeur n'essaie pas d'être + +Pas de coloration syntaxique avancée, pas de markdown riche : pour l'édition lourde, le bouton « Ouvrir dans l'éditeur externe » (paramétrable : VS Code, Notepad++, Obsidian…) ouvre le `.md`, et le surveillant de dossier répercute les changements au retour. + +--- + +## 10. Le moteur de variables + +### 10.1 Syntaxe + +`{{nom}}` dans le corps. Noms : `[a-z0-9_]+`, insensibles à la casse à la résolution. Aucune logique (pas de conditions, pas de boucles) en v1 : ce sont des **emplacements à remplir**, pas un langage de gabarit — la complexité est renvoyée au prompt lui-même, qui est fait pour une IA. + +### 10.2 Types de variables déclarées + +| Type | Contrôle du formulaire | Exemple | +|------|------------------------|---------| +| `text` | Champ ligne | `{{sujet}}` | +| `multiline` | Champ multiligne | `{{contexte}}` | +| `enum` | Liste déroulante (`choices` obligatoire) | `{{ton}}` | +| `number` | Champ + validation numérique | `{{nb_points}}` | +| `date` | Sélecteur de date, rendu `AAAA-MM-JJ` ou format du `default` | `{{echeance}}` | +| `bool` | Case à cocher, rend `default` si coché / texte alternatif `choices: [oui, non]` | `{{avec_exemples}}` | +| `password` | Champ masqué, **jamais persisté ni journalisé** | jeton ponctuel | + +Variable utilisée mais **non déclarée** : traitée comme `text` avec libellé = nom, demandée au formulaire (aucun blocage — un prompt copié-collé depuis le web doit fonctionner tel quel). + +### 10.3 Variables automatiques (jamais demandées) + +| Variable | Source | Précision | +|----------|--------|-----------| +| `{{selection}}` | Texte sélectionné dans l'application d'origine, capturé à l'invocation (§11.2) | Chaîne vide si rien n'est sélectionné ; le formulaire le signale | +| `{{clipboard}}` | Contenu texte du presse-papiers **avant** toute manipulation | Capturé avant que l'Injector ne touche au presse-papiers | +| `{{date}}` / `{{datetime}}` / `{{time}}` | Horloge locale | Formats `AAAA-MM-JJ`, `AAAA-MM-JJ HH:mm`, `HH:mm` | +| `{{app}}` | Nom du processus / titre de la fenêtre d'origine | Utile pour « je suis dans {{app}}… » | +| `{{cursor}}` | Marqueur de position : après injection, le curseur est replacé ici et le marqueur est retiré du texte | Pour les prompts-gabarits à compléter à la main | + +Règle de capture : les variables automatiques sont figées **au moment de l'invocation** (raccourci/hotstring), pas à la validation du formulaire — sinon la sélection d'origine serait perdue dès que le Picker prend le focus. + +### 10.4 Le formulaire de variables (§20.4) + +- Un contrôle par variable manuelle, dans l'ordre de déclaration ; valeurs par défaut pré-remplies ; `Tab` navigue, `Entrée` (hors champ multiligne) valide tout, `Échap` annule **toute** l'injection. +- Les `required` vides bloquent la validation avec surlignage, jamais avec une boîte de dialogue. +- Les choix faits sont mémorisés **par prompt** (`usage.json`, §15) comme nouveaux défauts proposés — un prompt « revue de code » retient que le langage habituel est Python. Les `password` sont exclus de cette mémorisation, sans exception. + +### 10.5 Ordre de résolution + +``` +corps du fichier + → substitution des automatiques (capturées à l'invocation) + → substitution des manuelles (formulaire ou défauts, Ctrl+Entrée) + → résolution de {{cursor}} (position mémorisée, marqueur retiré) + → préambule + séparateur + postambule du profil actif (§12.2) + → texte final remis à l'Injector +``` + +Aucune variable inconnue ne subsiste silencieusement : un `{{…}}` non résolu après ces étapes (ex. faute de frappe dans une automatique) est laissé tel quel **et** signalé dans la prévisualisation du formulaire ; en injection directe sans formulaire, un bip discret + infobulle tray « variable non résolue : X » après collage. Mieux vaut un défaut visible qu'un texte silencieusement faux. + +--- + +## 11. Le moteur d'injection : livrer le prompt à l'IA + +C'est le module le plus délicat du système. Il concentre toutes les interactions avec le focus, le presse-papiers et la frappe simulée. + +### 11.1 Interface + +``` +Injector.Deliver(texte, options) → résultat {ok, méthode, erreur?} + options : { hwndCible, profil, méthode: auto|clipboard|sendtext, + sendAfter: bool, restaurerPressePapiers: bool } +``` + +Aucun autre module n'appelle `Send`, `A_Clipboard` ou `ControlFocus` directement (règle vérifiable par recherche dans le code, §22). + +### 11.2 Capture du contexte d'invocation + +À chaque invocation (raccourci global, hotstring exclue) : + +1. `hwnd` et titre de la fenêtre active, nom du processus. +2. Tentative de capture de la sélection : sauvegarde du presse-papiers → `Ctrl+C` simulé → `ClipWait` court (250 ms) → lecture → restauration immédiate du presse-papiers. Si le presse-papiers n'a pas changé ou ne contient pas de texte, sélection = vide. Cette capture est **best-effort** : son échec ne bloque jamais l'ouverture du Picker. +3. Ces données vivent dans un objet `InvocationContext` transmis au Picker, au formulaire puis à l'Injector. + +### 11.3 Méthode « presse-papiers » (défaut) + +``` +1. Sauvegarder le presse-papiers complet (tous formats, via ClipboardAll). +2. Poser le texte final dans le presse-papiers (format texte Unicode). +3. Rendre le focus à hwndCible (WinActivate + attente active ≤ 500 ms). +4. Send "^v" ; attendre le délai de collage (par défaut 150 ms). +5. Planifier la restauration à +1,5 s (paramétrable) : + - si le presse-papiers contient encore EXACTEMENT le texte injecté → restaurer la sauvegarde ; + - sinon (l'utilisateur a copié autre chose) → abandonner la restauration sans rien écraser. +6. Si sendAfter (profil) : attendre 800 ms annulables, puis Send "{Enter}". +``` + +L'étape 5 est la différence entre un outil propre et un outil qui « mange » le presse-papiers de l'utilisateur : elle est obligatoire, pas optionnelle. + +### 11.4 Méthode « frappe simulée » (`SendText`) + +Utilisée quand : le profil de l'application la demande, le collage a échoué (détection best-effort), ou l'utilisateur la choisit pour cette fois (`Ctrl+Alt+V` dans le Picker bascule la méthode). `SendText` plutôt que `Send` : pas d'interprétation des caractères spéciaux (`+^!#{}`), indispensable pour du texte de prompt arbitraire. Vitesse plafonnée et documentée : au-delà de ~4 000 caractères, un avertissement propose de repasser en presse-papiers. + +### 11.5 Échecs et cas limites traités explicitement + +| Cas | Comportement | +|-----|--------------| +| Fenêtre d'origine fermée entre-temps | Injection annulée, texte final copié dans le presse-papiers + infobulle « fenêtre disparue — prompt copié » | +| Application cible élevée (admin), PromptKey non élevé | Message clair nommant l'application et l'option (relancer PromptKey en admin, ou copier plutôt qu'injecter) — détecté par comparaison d'intégrité du processus cible quand c'est possible, sinon par échec de `WinActivate` | +| Le champ cible n'accepte pas le collage (détecté : presse-papiers revenu inchangé n'est pas un signal fiable → pas de détection prétendue) | Aucune fausse promesse : le repli est un **choix de profil par application** (§12), pas une détection magique | +| `Échap` pendant le compte à rebours d'envoi | Envoi annulé, texte laissé en place | +| Texte final vide (prompt vide) | Refus avec message, aucune injection | +| PromptKey invoqué alors qu'il est déjà ouvert | Le Picker reprend le focus (instance unique) ; jamais deux fenêtres | + +### 11.6 Journalisation + +`promptkey.log` (rotation, 1 Mo) : horodatage, action, id du prompt, méthode, durée, erreur éventuelle. **Jamais** le contenu des prompts, des variables ou de la sélection (§18). + +--- + +## 12. Profils de destination (les « IA cibles ») + +### 12.1 Principe + +Un profil décrit **comment adapter la livraison** à une destination, pas un autre prompt. La bibliothèque reste unique ; les profils évitent les doublons « Résumer (pour Claude) », « Résumer (pour Qwen) »… + +### 12.2 Contenu d'un profil + +```ini +; config.ini — section [Profil.claude] +Nom=Claude +Preambule=Réponds en français, en Markdown, sans préambule de politesse.\n--- +Postambule= +Methode=clipboard +SendAfter=false +DelaiCollageMs=250 +AppMatch=chrome.exe, msedge.exe ; indices d'auto-détection (optionnel) +TitreMatch=Claude ; expression dans le titre de fenêtre +``` + +- **Préambule / postambule** : texte ajouté avant/après le corps résolu (avec `\n` interprétés). C'est l'endroit des consignes transversales : langue, format, concision. +- **Méthode, délais** : surcharge de l'injection pour cette destination. +- **`SendAfter`** : voir §12.3. +- Profils livrés : `défaut` (neutre), `claude`, `chatgpt`, `gemini`, `qwen`, `deepseek`, `hermes-terminal`, `courriel` — tous modifiables, liste ouverte. +- Un profil peut aussi être **générique par type de champ** : `terminal` (pas de Markdown, lignes courtes), `texte-brut`. + +### 12.3 Sélection du profil + +Dans l'ordre : (1) choix manuel dans le Picker (`Alt+n°`, mémorisé pour la session) ; (2) `targets`/`profile` du prompt (§7.2) ; (3) auto-détection par `AppMatch` + `TitreMatch` de la fenêtre d'origine ; (4) profil `défaut`. Le profil actif est toujours **visible** dans la barre de statut du Picker — jamais d'adaptation invisible. + +### 12.4 `SendAfter` : garde-fous + +Désactivé dans tous les profils livrés. Activation = case explicite dans les Paramètres **par profil**. Quand il est actif : compte à rebours visible dans une mini-fenêtre non bloquante (800 ms par défaut, paramétrable jusqu'à 3 s), `Échap` annule, et la première activation déclenche un avertissement unique rappelant le risque d'envoi au mauvais fil de discussion. + +--- + +## 13. Raccourcis globaux et hotstrings + +### 13.1 Raccourcis par défaut + +| Raccourci | Action | Remarque | +|-----------|--------|----------| +| `Ctrl+Alt+Espace` | Ouvrir le Quick Picker | **Le** raccourci. `Espace` évite la plupart des conflits applicatifs | +| `Ctrl+Alt+M` | Ouvrir le Gestionnaire | M = manager | +| `Ctrl+Alt+N` | Nouveau prompt (depuis la sélection si elle existe) | | +| `Ctrl+Alt+1…9` | Injecter l'épinglé n° 1…9 | `pinned:` de l'en-tête (§7.2) | +| `Ctrl+Alt+V` | Injecter le dernier prompt utilisé, à nouveau | Rejoue avec un contexte frais (nouvelle sélection capturée) | +| *(par prompt)* | Raccourci propre (`hotkey:`) | Priorité aux conflits signalés (§13.3) | + +Tous reassignables dans les Paramètres ; la modification ré-enregistre à chaud, sans redémarrage. + +### 13.2 Hotstrings + +Déclarée par `hotstring: resu;` → l'abréviation `::resu;` est active partout. Convention recommandée : terminer par `;` (ou un autre signe distinctif) pour éviter les déclenchements en pleine frappe normale — le document de l'utilisateur final le dira, et le modèle de création propose ce suffixe par défaut. + +Différences assumées vs raccourcis : pas de capture de contexte riche (la sélection environnante n'est pas récupérable au milieu d'une frappe) — les variables automatiques disponibles sont `{{clipboard}}`, `{{date}}`… mais **pas** `{{selection}}` ; si le prompt en contient une, elle est résolue comme vide et l'utilisateur est averti une fois par session. Les variables manuelles d'un prompt déclenché par hotstring ouvrent le même formulaire, puis l'injection se fait à l'endroit du curseur. + +Attention documentée : une hotstring se déclenche aussi dans les mots de passe et les terminaux ; la liste des hotstrings est visible d'un coup d'œil dans le Gestionnaire (filtre dédié) et chacune peut être désactivée sans supprimer le prompt. + +### 13.3 Conflits + +À l'enregistrement (démarrage, modification, création), tout échec de `Hotkey()`/`Hotstring()` est consigné et présenté dans Paramètres → Raccourcis en conflit, avec le prompt/raccourci concerné et trois issues : réassigner, désactiver, ignorer (consigné). Aucun conflit silencieux : un raccourci qui ne répond pas sans explication détruit la confiance dans l'outil. + +--- + +## 14. Recherche : index en mémoire et classement flou + +### 14.1 Champs indexés + +Par prompt : `title`, `tags`, `catégorie`, `corps` (tronqué aux 2 000 premiers caractères pour l'index), `id`. Normalisation : minuscules, suppression des accents (é→e) des deux côtés (requête et index) — « resu » doit trouver « Résumer ». + +### 14.2 Construction et cache + +Scan des `.md` → pour chacun : `mtime + taille` comparés à `index-cache.json` ; seuls les fichiers changés sont re-parsés. Démarrage cible : < 300 ms pour 1 000 prompts, cache chaud < 80 ms. Le cache est un détail d'optimisation jetable : le supprimer ne perd rien. + +### 14.3 Classement (détail en Annexe B) + +Score = pertinence textuelle (sous-séquence floue, bonus préfixe/mots entiers/titre) × pondérations d'usage (épinglé, favori, fréquence, récence). La recherche vide n'utilise que les pondérations (§8.3). L'algorithme tient en une fonction pure, testable unitairement avec des jeux fixes (§22) — le classement ne doit jamais devenir une boîte noire capricieuse entre deux versions. + +--- + +## 15. Historique, favoris et statistiques d'usage + +`usage.json` (à côté de la bibliothèque, **exclu de la synchro Git recommandée** — §17.3) : + +```json +{ + "7f3a2c1e-…": { + "count": 42, "last_used": "2026-10-11T09:32:11", + "last_values": { "ton": "direct", "langage": "Python" } + } +} +``` + +- `count` / `last_used` : alimentent le classement (§14.3) et le tri du Gestionnaire. +- `last_values` : derniers choix du formulaire → nouveaux défauts proposés (§10.4). Jamais de `password`, jamais `{{selection}}`/`{{clipboard}}` (ce sont des contextes, pas des préférences). +- Favoris et épinglés vivent dans l'**en-tête** du prompt (ils font partie de sa définition partageable), les statistiques dans `usage.json` (elles sont personnelles et volatiles). Cette séparation est intentionnelle et cohérente avec D2/§7.5. +- « Réinitialiser les statistiques » dans les Paramètres ; historique détaillé (liste des usages horodatés) **non** conservé : seuls les compteurs servent le classement, le reste serait de la surveillance de soi sans bénéfice. + +--- + +## 16. Import, export et packs de prompts + +| Opération | Format | Comportement | +|-----------|--------|--------------| +| Export d'une sélection / catégorie | `.zip` de `.md` (+ `pack.json` de manifeste) | Les fichiers restent de simples `.md` : le zip n'est qu'un transport | +| Export « texte brut » | Un `.md` unique découpé par `## Titre` | Pour partager dans un courriel ou une note ; réimportable | +| Import | `.md`, dossier de `.md`, `.zip`, `.json` (schéma §16.1) | Prévisualisation : nouveaux / doublons (même `id`) / conflits (même titre, autre `id`) — choix par ligne : importer, remplacer, dupliquer, ignorer | +| Import du presse-papiers | Texte quelconque | Crée un prompt dont le corps est le contenu, titre à saisir | +| Pack de démarrage | Dossier `_packs\essentiels\` | Livré avec l'application (Annexe D), installable/ignoré au premier lancement | + +### 16.1 Schéma JSON d'échange (pour l'interopérabilité) + +```json +{ "promptkey_export": 1, + "prompts": [ { "title": "…", "category": "Code", "tags": ["…"], + "body": "… {{selection}} …", "variables": [ { "name": "ton", "type": "enum", "choices": ["…"] } ] } ] } +``` + +L'import JSON **génère** des fichiers `.md` conformes à §7 (D2 reste la loi : aucun prompt ne vit durablement en JSON). + +--- + +## 17. Paramètres, portabilité et synchronisation + +### 17.1 `config.ini` (à côté de l'exécutable ou de la bibliothèque, mode portable) + +Sections : `[General]` (dossier bibliothèque, langue d'interface du Picker — FR par défaut, thème, démarrage Windows), `[Injection]` (méthode par défaut, délais, restauration), `[Raccourcis]`, `[Editeur]` (éditeur externe), `[Profil.*]` (§12.2). INI plutôt que JSON pour les paramètres : éditable à la main sans casse, natif en AHK (`IniRead/IniWrite`). + +### 17.2 Mode portable intégral + +Si `PromptKey.exe` trouve un dossier `Bibliothèque\` à côté de lui, il l'utilise et écrit tout (config, cache, stats) dans son propre dossier : la totalité de l'outil — programme + prompts + réglages — tient sur une clé USB ou dans un dossier synchronisé. Sinon, défaut : `%USERPROFILE%\Documents\PromptKey\`. + +### 17.3 Synchronisation (par le dossier, jamais par le code) + +- **Git/Gitea** (recommandé pour Bruno) : la bibliothèque est un dépôt ; `usage.json` et `index-cache.json` dans `.gitignore`. Historique, retour arrière, partage entre postes. +- **OneDrive / dossier partagé** : fonctionne tel quel ; le surveillant de dossier (§6.3) absorbe les arrivées de fichiers. +- Conflits : PromptKey ne fusionne rien. Un fichier modifié des deux côtés relève de Git ou de l'Explorateur ; l'application relit ce qui est sur disque, point. Ce non-objectif est écrit ici pour ne jamais être « ajouté vite fait » plus tard. + +--- + +## 18. Sécurité et vie privée + +1. **Zéro réseau** (D7) : vérifiable — le code ne contient aucun `WinHttp`, `curl` ou socket. C'est aussi l'argument antivirus. +2. **Aucun contenu journalisé** : ni prompts résolus, ni sélections, ni valeurs de variables dans `promptkey.log` (§11.6). +3. **Variables `password`** : masquées, non persistées (§10.4), exclues de `last_values`, et le texte final qui les contient n'est jamais mis dans le presse-papiers plus longtemps que le délai d'injection — préférer `SendText` pour ces prompts (réglage automatique proposé à la déclaration d'une telle variable). +4. **Secrets dans les prompts eux-mêmes** : déconseillés par conception (fichiers en clair, versionnés). La documentation utilisateur le dira en première page ; un avertissement de l'Éditeur signale les motifs évidents (`sk-…`, `api_key=`, `Bearer …`) — avertissement, pas censure. +5. **Injection ≠ exécution** : PromptKey colle du **texte**. Si le texte collé dans un terminal est une commande, c'est l'affaire de l'utilisateur — d'où D5 (pas d'envoi automatique par défaut), particulièrement sensible pour le profil `terminal`. +6. **Presse-papiers restauré** (§11.3) : ne pas laisser le dernier prompt — possiblement sensible — traîner dans l'historique du presse-papiers Windows (`Win+V`) plus que nécessaire. Limite honnête : Windows peut avoir déjà archivé la valeur ; le paramètre « ne pas restaurer mais **vider** après injection » est offert pour les prompts marqués `sensitive: true` dans l'en-tête (champ ajouté au contrat §7.2). + +--- + +## 19. Architecture du code AutoHotkey v2 + +### 19.1 Arborescence du projet source + +``` +PromptKey\ +├─ PromptKey.ahk ← point d'entrée, #Include, bootstrap, tray +├─ lib\ +│ ├─ Settings.ahk ← config.ini (lecture/écriture, défauts) +│ ├─ PromptStore.ahk ← scan dossier, parse/write des .md (§7) +│ ├─ Frontmatter.ahk ← parseur du sous-ensemble §7.3 (+ tests) +│ ├─ Index.ahk ← index mémoire, cache, recherche floue (§14) +│ ├─ VariableEngine.ahk ← résolution §10.5, captures automatiques +│ ├─ ClipboardGuard.ahk ← sauvegarde/restauration sûre (§11.3) +│ ├─ Injector.ahk ← SEUL module autorisé à Send / coller (§11.1) +│ ├─ HotkeyManager.ahk ← enregistrement, conflits (§13) +│ ├─ UsageStats.ahk ← usage.json (§15) +│ └─ Util.ahk ← dates, normalisation accents, GUID, log +├─ gui\ +│ ├─ Picker.ahk ← §8 / §20.1 +│ ├─ Manager.ahk ← §9.1 / §20.2 +│ ├─ Editor.ahk ← §9.2 / §20.3 +│ ├─ VarForm.ahk ← §10.4 / §20.4 +│ └─ SettingsGui.ahk ← paramètres, conflits, profils +├─ assets\ (icônes .ico : clair/sombre, épinglé…) +├─ packs\essentiels\ ← Annexe D +├─ tests\ ← §22 (scripts AHK autonomes, sortie console) +├─ build.ahk / build.ps1 ← compilation Ahk2Exe + zip de distribution +└─ README.md +``` + +### 19.2 Principes de code + +- Une classe par module, état passé explicitement ; pas de variables globales hors `Settings` et `App` (contexte courant). +- Les GUI ne touchent ni au disque ni au presse-papiers : elles appellent les services. L'`Injector` ne connaît pas les GUI : il reçoit un texte et des options. +- Erreurs : les modules lèvent/retournent des résultats typés `{ok, erreur}` ; les messages utilisateur sont en français, centralisés dans `Util.Msg*` pour une éventuelle traduction. +- Aucune dépendance externe en v1 (pas de DLL, pas de librairie tierce) : le `.ahk` doit tourner sur une installation AHK v2 propre. + +### 19.3 Concurrence et minuteries + +Processus à un fil : tout est événementiel (raccourcis, minuteries AHK). Le scan initial de grosses bibliothèques est le seul travail long : chargement par lots avec `SetTimer` pour ne pas geler l'icône tray, barre de progression discrète dans le Gestionnaire si > 1 000 fichiers. Aucun travail de fond pendant qu'un Picker est ouvert (le surveillant est suspendu, repris à la fermeture). + +### 19.4 Encodage — le piège n° 1 en AHK + +Règle unique : les fichiers prompts sont lus et écrits en **UTF-8 explicite** (`FileEncoding` positionné et restauré, ou `FileOpen` avec encodage nommé), jamais dans l'encodage ANSI par défaut. Les prompts contiennent des accents, des « — », des émojis : un seul aller-retour ANSI les détruit. Tests dédiés (§22) : aller-retour d'un corps contenant `é à — ‘’ « » 😀` et `{{variables}}` doit être **bit à bit identique**. Les fins de ligne sont normalisées en `CRLF` à l'écriture sous Windows, tolérées en `LF` à la lecture. + +### 19.5 Fenêtres et DPI + +`Gui` créés avec prise en charge DPI (`-DPIScale` évité, dimensions en unités relatives, polices `Segoe UI` 10/11). Picker : largeur fixe confortable (~720 px à 100 %), centrée sur l'écran de la fenêtre d'origine (pas forcément l'écran principal). Le retour de focus (§6.2) cible le `hwnd` capturé, jamais « la fenêtre sous la souris ». + +--- + +## 20. Maquettes des écrans (ASCII) + +### 20.1 Quick Picker + +``` +┌─ PromptKey ──────────────────────────────────────────── [profil: Claude ▾] ─┐ +│ Rechercher : résu█ │ +├──────────────────────────────────┬──────────────────────────────────────────┤ +│ ★ Résumer en 5 points [Général]│ Résume le texte suivant en {{longueur}}, │ +│ 📌 Revue de code [Code] │ ton {{ton}}. Réponds en français… │ +│ Résumer un courriel [Écrit.]│ │ +│ Résumé exécutif (1 page)[Gén.] │ Texte : │ +│ │ {{selection}} │ +│ │ │ +│ │ Sélection capturée : « Le rapport du │ +│ │ troisième trimestre montre… » (1 240 c.) │ +├──────────────────────────────────┴──────────────────────────────────────────┤ +│ Entrée Utiliser · Ctrl+Entrée Défauts · Ctrl+E Modifier · 12 résultats │ +│ Méthode : presse-papiers · Envoi auto : non │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 20.2 Gestionnaire + +``` +┌─ PromptKey — Gestionnaire ──────────────────────────────────────────────────┐ +│ Fichier Prompt Outils Rechercher : [____________] │ +├───────────────┬──────────────────────────────────┬──────────────────────────┤ +│ ▾ Bibliothèque│ Titre Modifié Usage│ Résumer en 5 points │ +│ Code (4)│ ★ Résumer 5 points 10-09 42 │ Catégorie : Général │ +│ Écriture (6)│ 📌 Revue de code 10-10 31 │ Étiquettes : résumé, … │ +│ Général (9)│ Traduire FR→EN 09-28 18 │ Raccourci : — Hotstring │ +│ ★ Favoris (5)│ Expliquer erreur 10-11 3 │ : resu; │ +│ ◷ Récents │ │ │ +│ 📌 Épinglés(3)│ │ [Utiliser] [Modifier] │ +│ ⚠ En erreur(1)│ │ [Dupliquer] [Exporter] │ +│ │ │ [Supprimer] │ +├───────────────┴──────────────────────────────────┴──────────────────────────┤ +│ [+ Nouveau] [Depuis sélection] [Importer] 20 prompts · index à jour │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 20.3 Éditeur + +``` +┌─ Modifier — Revue de code ───────────────────────┬─ Métadonnées ────────────┐ +│ Tu es un relecteur de code senior. │ Titre : Revue de code │ +│ │ Catégorie : [Code ▾] │ +│ Relis le {{langage}} suivant (niveau {{niveau}}) │ Étiquettes : code, revue │ +│ et signale : │ ★ Favori 📌 Épinglé n°2 │ +│ 1. les bogues probables… │ Raccourci : [Ctrl+Alt+R] │ +│ │ Hotstring : [revue; ] │ +│ Code : │ Profils : [claude, qwen] │ +│ {{selection}} │ │ +│ │ Variables déclarées : │ +│ │ • langage (enum) │ +│ │ • niveau (enum) │ +│ [Insérer variable Ctrl+J] │ [+ Déclarer] │ +├──────────────────────────────────────────────────┴──────────────────────────┤ +│ ⚠ « niveau » utilisé mais non déclaré — [Déclarer] [Tester Ctrl+T] │ +│ [Ouvrir externe] [Annuler] [OK] │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 20.4 Formulaire de variables + +``` +┌─ Revue de code — paramètres ──────────────┐ +│ Langage : [Python ▾] │ +│ Niveau : (•) Strict ( ) Rapide │ +│ Contexte : [___________________________] │ +│ [___________________________] │ +│ │ +│ Sélection incluse : 1 240 caractères ✓ │ +│ [Échap Annuler] [Entrée OK] │ +└───────────────────────────────────────────┘ +``` + +--- + +## 21. Packaging, installation et mises à jour + +### 21.1 Formes de distribution + +1. **Source `.ahk`** (pour qui a AHK v2) — la forme de référence, lisible et auditable. +2. **`PromptKey.exe`** compilé par Ahk2Exe (base AHK v2 64 bits, icône intégrée) — pour l'usage quotidien. +3. **Archive portable `.zip`** : exe + `packs\` + `README` + bibliothèque d'exemple ; aucune installation, mode portable (§17.2) actif par la présence du dossier `Bibliothèque\`. + +Pas d'installeur en v1 : décompresser, lancer, éventuellement cocher « Démarrer avec Windows » (raccourci dans le dossier Démarrage, créé par l'application sur demande — pas d'écriture registre cachée). + +### 21.2 Premier lancement + +Assistant en trois écrans maximum : (1) choisir/créer le dossier de bibliothèque ; (2) proposer le pack de démarrage (Annexe D) — installé seulement sur accord ; (3) montrer LE raccourci, avec bouton « Tester maintenant » qui ouvre le Picker. Toute la valeur de l'outil tient à ce premier test réussi en moins d'une minute. + +### 21.3 Compilation reproductible + +`build.ps1` : vérifie la version AHK, lance les tests (§22), compile, produit le zip et un `SHA256`. La recette est dans le README : n'importe qui peut recompiler et comparer — réponse directe au problème des faux positifs antivirus (§4.2). + +### 21.4 Mises à jour + +Manuelles en v1 (D7) : remplacer l'exe. Les formats de fichiers (`.md`, `config.ini`, `usage.json`) portent un numéro de version interne ; une migration de format, si un jour nécessaire, se fait à la lecture avec sauvegarde préalable, et est annoncée dans le journal. + +--- + +## 22. Tests et critères de qualité + +Pas de framework lourd : des scripts AHK autonomes dans `tests\`, exécutables en ligne de commande, sortie `OK/ÉCHEC` — lancés par `build.ps1` avant toute compilation. + +| Suite | Ce qui est vérifié | +|-------|--------------------| +| `Frontmatter` | Le contrat §7.3 : fichiers valides parsés au champ près ; chaque interdit produit l'erreur et la ligne attendues ; fichier sans en-tête accepté | +| `Encodage` | Aller-retour UTF-8 bit à bit (§19.4), fins de ligne LF/CRLF | +| `Variables` | Résolution §10.5, automatiques, `{{cursor}}`, inconnues signalées, `password` jamais dans `last_values` | +| `Recherche` | Jeux fixes titre/requête → ordre attendu (Annexe B), insensibilité aux accents | +| `Injection` (manuel assisté) | Script ouvrant le Bloc-notes : collage, restauration du presse-papiers, abandon si copie intermédiaire, `SendText`, retour de focus — **à exécuter sur le poste réel**, cases cochées dans le rapport de test | +| `Raccourcis` | Enregistrement des défauts sans conflit sur une machine propre ; rapport de conflit simulé | + +**Golden tasks** (parcours complets, chronométrés à la main avant chaque version) : + +1. Démarrage froid → Picker → prompt sans variable collé dans le Bloc-notes : < 3 s, presse-papiers d'origine intact après coup. +2. Prompt à 3 variables dont une `enum` : formulaire → collage correct dans un navigateur (page de test locale). +3. Hotstring `resu;` dans Word : remplacement + variables résolues. +4. Édition d'un `.md` dans un éditeur externe pendant que PromptKey tourne : index à jour sans redémarrage. +5. Bibliothèque de 2 000 prompts générés : démarrage < 1 s cache chaud, frappe dans le Picker sans latence visible. + +--- + +## 23. Plan d'implémentation en phases (tickets) + +Convention : chaque ticket est livrable et testable seul ; les critères d'acceptation sont des comportements observables, pas des intentions. Estimation en demi-journées (dj) pour un développeur AHK v2 à l'aise — à titre d'ordre de grandeur. + +### Phase 0 — Socle et preuve du geste central (≈ 2 dj) + +Objectif : **prouver le risque n° 1** — capture du focus, collage propre, presse-papiers restauré — avant tout le reste. + +| Ticket | Contenu | Acceptation | +|--------|---------|-------------| +| P0-1 | Squelette : `PromptKey.ahk`, tray, instance unique, `Settings` (INI) | L'exe démarre, icône tray, quitter fonctionne, aucun résidu | +| P0-2 | `ClipboardGuard` + `Injector` (méthode presse-papiers seule) | Golden task 1 réussie sur Bloc-notes et navigateur | +| P0-3 | Raccourci global + capture du contexte (hwnd, sélection best-effort, §11.2) | La sélection d'un paragraphe est récupérée 9 fois sur 10 dans Word/Chrome ; échec = sélection vide, jamais blocage | +| P0-4 | Picker minimal : liste fixe de 5 prompts codés en dur, recherche naïve, `Entrée`/`Échap` | Le geste §1 complet fonctionne de bout en bout | + +### Phase 1 — Bibliothèque réelle (≈ 3 dj) + +| Ticket | Contenu | Acceptation | +|--------|---------|-------------| +| P1-1 | `Frontmatter` : parseur du contrat §7.3 + écriture (aller-retour) | Suite `Frontmatter` verte ; erreurs avec n° de ligne | +| P1-2 | `PromptStore` : scan, catégories-dossiers, génération d'`id`, surveillant de dossier | §6.3 : un `.md` déposé à la main apparaît sans redémarrage | +| P1-3 | Index + cache + recherche floue (Annexe B) | Suite `Recherche` verte ; 1 000 prompts, < 50 ms/frappe | +| P1-4 | Picker complet (§8) : aperçu, tris par défaut, barre de statut | Golden tasks 1 et 5 | +| P1-5 | Gestionnaire (§9.1) : arbre, liste, détail, CRUD, corbeille | Créer → utiliser → modifier → supprimer sans quitter l'outil | + +### Phase 2 — Variables et profils (≈ 3 dj) + +| Ticket | Contenu | Acceptation | +|--------|---------|-------------| +| P2-1 | `VariableEngine` : types §10.2, automatiques §10.3, `{{cursor}}`, ordre §10.5 | Suite `Variables` verte | +| P2-2 | Formulaire de variables (§20.4) + mémorisation des derniers choix | Golden task 2 ; `password` absent de `usage.json` (vérifié) | +| P2-3 | Profils (§12) : préambule, méthode, sélection auto/manuelle, `SendAfter` gardé | Même prompt, deux profils → deux textes finaux conformes en prévisualisation | +| P2-4 | Éditeur (§9.2) : métadonnées, insertion/déclaration de variables, validation, Tester | Un prompt créé de zéro dans l'Éditeur fonctionne au Picker sans retouche du fichier | +| P2-5 | Injection `SendText` + bascule par profil/par frappe | Un champ où le collage échoue reçoit le prompt en frappe, texte exact | + +### Phase 3 — Raccourcis avancés, import/export (≈ 2 dj) + +| Ticket | Contenu | Acceptation | +|--------|---------|-------------| +| P3-1 | `HotkeyManager` : raccourcis par prompt, épinglés `1…9`, rapport de conflits (§13.3) | Conflit provoqué → visible et explicable dans Paramètres | +| P3-2 | Hotstrings (§13.2) + filtre de gestion | Golden task 3 ; désactivation unitaire respectée | +| P3-3 | `UsageStats` complet : tris Fréquents/Récents, réinitialisation | §8.3 vérifié après usages simulés | +| P3-4 | Import/export (§16) + pack de démarrage (Annexe D) + premier lancement (§21.2) | Export d'une catégorie réimporté sur une bibliothèque vierge = prompts identiques | +| P3-5 | Paramètres complets (§17.1), mode portable, démarrage Windows | Le zip portable fonctionne depuis un dossier neuf, sans trace ailleurs | + +### Phase 4 — Durcissement et distribution (≈ 2 dj) + +| Ticket | Contenu | Acceptation | +|--------|---------|-------------| +| P4-1 | Suites de tests complètes (§22) intégrées à `build.ps1` | Build refusé si un test échoue | +| P4-2 | Compilation Ahk2Exe, zip portable, sommes SHA256, README utilisateur (français) | Une machine sans AHK utilise le zip de bout en bout | +| P4-3 | Cas limites §11.5 passés en revue un par un sur poste réel | Tableau §11.5 coché ligne par ligne | +| P4-4 | Prompts marqués `sensitive` : vidage du presse-papiers après injection (§18, point 6) | Après injection d'un tel prompt, `Win+V` ne montre pas le texte injecté comme entrée PromptKey restaurée | + +### Phase 5 — Extensions optionnelles (hors v1, à décider séparément) + +- **P5-A — Pont API** : profil spécial qui, au lieu d'injecter, envoie le prompt résolu à une API (OpenAI-compatible) et colle la **réponse** — change la nature de l'outil (réseau, clés, coûts) : décision explicite requise, contraire à D7. +- **P5-B — Chaînes de prompts** : un prompt dont une variable est la sortie d'un autre (composition), sans langage de gabarit complet. +- **P5-C — Bibliothèque SQLite** : si la bibliothèque dépasse ~5 000 prompts (D3 à réviser, D2 inchangé). +- **P5-D — Snippets système** : réponses types courriel/support gérées dans le même outil (mêmes mécanismes, autre bibliothèque). + +--- + +## 24. Risques et mitigations + +| Risque | Prob. | Mitigation (section) | +|--------|-------|----------------------| +| Le retour de focus échoue dans certaines applications → texte perdu ou au mauvais endroit | Moyenne | Module isolé et testé (§11), fenêtre disparue = copie de secours, jamais d'envoi auto par défaut (D5) | +| Presse-papiers de l'utilisateur écrasé ou historique pollué | Moyenne | `ClipboardGuard` + restauration conditionnelle (§11.3), mode `sensitive` (§18, point 6) | +| Faux positifs antivirus sur l'exe compilé | Élevée | Source distribuée, build reproductible (§21.3), zéro réseau (D7) | +| Le sous-ensemble d'en-tête devient un faux YAML frustrant | Moyenne | Contrat écrit (§7.3), erreurs avec ligne, fichiers sans en-tête acceptés, Éditeur qui génère l'en-tête pour l'utilisateur | +| Conflits de raccourcis avec les applications du quotidien | Élevée | Défaut peu conflictuel (`Ctrl+Alt+Espace`), détection et rapport (§13.3) | +| L'utilisateur empile 300 prompts et ne retrouve rien | Moyenne | Classement par usage (§14.3), épinglés/favoris, Annexe D comme modèle de nommage | +| Verrouillage format : les prompts deviennent prisonniers de PromptKey | Faible | D2 : des `.md` lisibles restent exploitables même sans l'application — c'est un argument de conception, pas un risque résiduel | + +--- + +## 25. Alternatives écartées + +| Alternative | Pourquoi pas (pour ce besoin) | +|-------------|-------------------------------| +| **Espanso / Beeftext / PhraseExpress** (extenseurs de texte) | Excellents pour des snippets **figés** ; pas de bibliothèque de prompts cherchable avec aperçu, profils par IA, ni formulaire de variables riche au centre du geste | +| **PowerToys / lanceurs (Flow Launcher, Raycast-like)** | Bons lanceurs, mais l'injection fine (capture de sélection, restauration du presse-papiers, profils) y serait un plugin à écrire quand même — AHK la donne nativement | +| **Les bibliothèques intégrées des IA** (prompts enregistrés ChatGPT/Claude…) | Enfermées par outil : le besoin est justement **une** bibliothèque pour **toutes** les IA (§2.1-4) | +| **Gestionnaire de presse-papiers (Ditto…)** | Retrouve des textes collés, ne gère ni variables, ni catégories, ni profils | +| **Application Electron/web maison** | Surdimensionné : installation, empreinte, et l'accès global au clavier/presse-papiers sous Windows y est plus indirect qu'en AHK | +| **Simples hotstrings AHK sans application** | Le point de départ de beaucoup d'usages réels — mais sans Picker ni gestion, la bibliothèque cesse d'être retrouvable dès ~20 prompts ; PromptKey les conserve comme un mode d'accès parmi d'autres (§13.2) | + +--- + +## 26. Décisions ouvertes pour Bruno + +Aucune ne bloque la Phase 0 ; Q2 et Q3 influencent la Phase 1. + +- **Q1 — Nom.** « PromptKey » est un nom de travail. Alternatives : PromptDeck (écho à Flowdeck), Prompto, TouchePrompt… Le nom décide du dossier par défaut et de l'icône. +- **Q2 — Bibliothèque initiale.** Dossier neuf dédié, ou adosser la bibliothèque à un vault/dossier existant (les `.md` y côtoieraient d'autres notes — le parseur tolère les fichiers sans en-tête, mais le Gestionnaire ne montrera que le dossier choisi) ? +- **Q3 — Synchro entre postes.** Git/Gitea dès le départ (recommandé, §17.3) ou dossier local simple en v1 ? +- **Q4 — Langue d'interface.** Français seulement en v1 (hypothèse de ce document) ou FR/EN dès le départ ? Le code centralise les messages (§19.2) pour garder la porte ouverte. +- **Q5 — `SendAfter`.** Le conserver comme option gardée (§12.4), ou le retirer complètement de la v1 pour ne jamais risquer un envoi involontaire — quitte à le réintroduire plus tard ? + +--- + +## Annexe A — Exemple complet d'un fichier prompt + +Fichier : `Bibliothèque\Code\expliquer-une-erreur.md` + +```markdown +--- +id: 9d41b7aa-3c2f-4e01-9a55-1f0c2d77e810 +title: Expliquer une erreur +tags: [débogage, erreur, pédagogie] +targets: [défaut, claude, qwen, deepseek] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: niveau + label: Mon niveau sur ce sujet + type: enum + choices: [débutant, intermédiaire, avancé] + default: intermédiaire +--- + +Explique l'erreur ci-dessous à un niveau {{niveau}} : +1. ce qu'elle signifie, en une phrase simple ; +2. la cause la plus probable dans ce code ; +3. la correction minimale, avec le code corrigé seulement. + +Message d'erreur et code : +{{selection}} +``` + +Utilisation type : copier un message d'erreur (ou le sélectionner dans l'IDE) → `Ctrl+Alt+Espace` → « erreur » → choisir le niveau → le prompt complet est collé dans l'IA ouverte. + +## Annexe B — Algorithme de classement de la recherche + +Fonction pure `Score(prompt, requêteNormalisée)` : + +1. **Correspondance floue** : la requête (sans accents, minuscules) est cherchée en sous-séquence dans titre, puis étiquettes, puis catégorie, puis corps. Bonus : correspondance en début de mot (×2), mot entier (×3), dans le titre (×4). +2. **Requête multi-mots** : chaque mot doit correspondre quelque part (ET logique) ; le score est la somme des mots. +3. **Pondérations multiplicatives** : épinglé ×1,5 · favori ×1,3 · fréquence `1 + log10(1+count) × 0,2` · récence : ×1,2 si utilisé < 7 jours. +4. **Tri** : score décroissant, égalité tranchée par `last_used` puis titre alphabétique — déterminisme total, testable (§22). + +Le but n'est pas la sophistication : c'est que les 5 prompts quotidiens remontent en ≤ 3 frappes, et que le comportement soit **prévisible**. + +## Annexe C — Séquence d'injection, pas à pas + +``` +Utilisateur Application X PromptKey Presse-papiers + │ │ │ │ + │── Ctrl+Alt+Esp ────┼─────────────────▶│ capture hwnd, titre │ + │ │ │ capture sélection ──────▶│ (sauvegarde, + │ │ │ │ Ctrl+C, lecture, + │ │ │◀───────────────────────│ restauration) + │ │ Picker ouvert │ │ + │── frappe+Entrée ───┼─────────────────▶│ résolution variables │ + │ │ │ texte final (profil) │ + │ │ │ sauvegarde complète ──▶│ + │ │ │ pose le texte ────────▶│ + │ │◀─ WinActivate ───│ │ + │ │◀─ Ctrl+V ────────│ │ + │ │ (texte collé) │ minuterie +1,5 s │ + │ │ │ si PP inchangé : ─────▶│ restauration + │ │ │ sinon : abandon propre │ +``` + +## Annexe D — Pack de démarrage proposé (20 prompts) + +Livrés dans `packs\essentiels\`, chacun conforme à §7, avec variables déclarées — ils servent aussi d'exemples du format. + +**Général** : Expliquer simplement · Expliquer une erreur (Annexe A) · Résumer en 5 points · Plan d'action à partir d'un texte · Questions de clarification avant de répondre. +**Écriture** : Reformuler (ton variable) · Corriger sans changer le style · Raccourcir de moitié · Courriel poli à partir de notes brutes · Traduire FR↔EN en gardant la mise en forme. +**Code** : Revue de code (niveau variable) · Écrire un test pour cette fonction · Expliquer ce code ligne par ligne · Convertir dans un autre langage · Message de commit à partir du diff (`{{clipboard}}`). +**Analyse** : Pour / contre structuré · Extraire les décisions et tâches d'un compte rendu · Comparer deux options selon mes critères (`{{contexte}}`) · Résumé exécutif d'une page · Transformer en tableau Markdown. + +--- + +*Fin du document — PromptKey, architecture et conception v1.0.* diff --git a/packs/essentiels/Analyse/comparer-deux-options.md b/packs/essentiels/Analyse/comparer-deux-options.md new file mode 100644 index 0000000..8aec88e --- /dev/null +++ b/packs/essentiels/Analyse/comparer-deux-options.md @@ -0,0 +1,23 @@ +--- +id: 712359c5-f741-4439-8ca9-a795d7ee41b8 +title: Comparer deux options +tags: [analyse, comparaison, décision] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: contexte + label: Mes critères de choix + type: multiline + required: false +--- + +Compare les deux options décrites ci-dessous, selon mes critères : {{contexte}} + +- Présente un tableau Markdown : Critère | Option A | Option B — une ligne par critère pertinent, en commençant par mes critères s'ils sont fournis. +- Après le tableau, donne les avantages décisifs de chaque option en une phrase. +- Termine par une recommandation argumentée, et précise quelle information supplémentaire pourrait la faire changer. + +Options : +{{selection}} diff --git a/packs/essentiels/Analyse/extraire-decisions-et-taches.md b/packs/essentiels/Analyse/extraire-decisions-et-taches.md new file mode 100644 index 0000000..ed12a66 --- /dev/null +++ b/packs/essentiels/Analyse/extraire-decisions-et-taches.md @@ -0,0 +1,18 @@ +--- +id: 0f5a51ee-e852-4a26-8fda-a5def71136eb +title: Extraire décisions et tâches +tags: [compte rendu, décisions, tâches, extraction] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +--- + +À partir du compte rendu ci-dessous, extrais uniquement ce qui y figure réellement : + +1. **Décisions prises** — liste, une décision par ligne, formulée comme dans le texte. +2. **Tâches** — tableau Markdown : Tâche | Responsable | Échéance. Si le responsable ou l'échéance n'est pas indiqué, écris « à définir » — n'invente rien. +3. **Points en suspens** — questions ouvertes ou sujets reportés. + +Compte rendu : +{{selection}} diff --git a/packs/essentiels/Analyse/pour-contre-structure.md b/packs/essentiels/Analyse/pour-contre-structure.md new file mode 100644 index 0000000..0df8160 --- /dev/null +++ b/packs/essentiels/Analyse/pour-contre-structure.md @@ -0,0 +1,26 @@ +--- +id: c2bf8e1f-0ab4-45a5-990a-132ca10ebe2f +title: Pour / contre structuré +tags: [analyse, décision, pour-contre] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: contexte + label: Contexte ou critères personnels + type: multiline + required: false +--- + +Analyse la décision ou le sujet ci-dessous de façon structurée. + +1. **Pour** — les arguments favorables, du plus fort au plus faible. +2. **Contre** — les arguments défavorables, du plus fort au plus faible. +3. **Conditions** — ce qui ferait pencher la balance d'un côté ou de l'autre. +4. **Conclusion** — une recommandation en deux phrases, en tenant compte du contexte fourni (s'il y en a un). + +Contexte : {{contexte}} + +Sujet : +{{selection}} diff --git a/packs/essentiels/Analyse/resume-executif.md b/packs/essentiels/Analyse/resume-executif.md new file mode 100644 index 0000000..73d7961 --- /dev/null +++ b/packs/essentiels/Analyse/resume-executif.md @@ -0,0 +1,30 @@ +--- +id: 77d2e25e-08fb-454e-9178-973248fdd7e9 +title: Résumé exécutif +tags: [analyse, résumé, direction] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: longueur + label: Longueur visée + type: enum + choices: ["1 page", "10 lignes", "5 lignes"] + default: "1 page" + - name: public + label: Public du résumé + type: enum + choices: [direction, équipe, client] + default: direction +--- + +Rédige un résumé exécutif du document ci-dessous (longueur : {{longueur}} ; public : {{public}}). + +- Ouvre sur la conclusion ou la décision essentielle, pas sur le contexte. +- Puis : les 3 à 5 faits qui comptent, les risques ou points d'attention, et la suite attendue. +- Style factuel, phrases courtes, aucun jargon inutile ; conserve les chiffres clés. +- N'ajoute aucune information absente du document. + +Document : +{{selection}} diff --git a/packs/essentiels/Analyse/transformer-en-tableau-markdown.md b/packs/essentiels/Analyse/transformer-en-tableau-markdown.md new file mode 100644 index 0000000..023c096 --- /dev/null +++ b/packs/essentiels/Analyse/transformer-en-tableau-markdown.md @@ -0,0 +1,25 @@ +--- +id: 7ffb9069-4566-4741-b0f0-1409ec1fb10c +title: Transformer en tableau Markdown +tags: [analyse, tableau, markdown, données] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: tri + label: Tri des lignes + type: enum + choices: [ordre d'apparition, alphabétique, aucun] + default: ordre d'apparition +--- + +Transforme les données ci-dessous en un tableau Markdown propre (tri des lignes : {{tri}}). + +- Déduis les colonnes du contenu ; en-têtes courts et explicites. +- Une information par cellule ; si une valeur manque, laisse la cellule vide. +- Aligne les nombres à droite si le format le permet. +- Donne uniquement le tableau, sans commentaire avant ni après. + +Données : +{{selection}} diff --git a/packs/essentiels/Code/convertir-dans-un-autre-langage.md b/packs/essentiels/Code/convertir-dans-un-autre-langage.md new file mode 100644 index 0000000..1f93212 --- /dev/null +++ b/packs/essentiels/Code/convertir-dans-un-autre-langage.md @@ -0,0 +1,25 @@ +--- +id: bb28f77f-bf2a-4f46-bd3f-e18be6e47736 +title: Convertir dans un autre langage +tags: [code, conversion, portage] +targets: [défaut, claude, chatgpt, qwen, deepseek] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: langage_cible + label: Langage cible + type: enum + choices: [Python, JavaScript, TypeScript, Go, Rust, AutoHotkey v2] + default: Python +--- + +Convertis le code ci-dessous en {{langage_cible}}. + +- Comportement identique : mêmes entrées, mêmes sorties, mêmes cas d'erreur. +- Code idiomatique dans le langage cible, pas une traduction mot à mot. +- Si une bibliothèque d'origine n'a pas d'équivalent direct, signale-le en commentaire à l'endroit concerné. +- Donne uniquement le code converti, avec un court commentaire d'en-tête indiquant la source. + +Code source : +{{selection}} diff --git a/packs/essentiels/Code/ecrire-un-test-pour-cette-fonction.md b/packs/essentiels/Code/ecrire-un-test-pour-cette-fonction.md new file mode 100644 index 0000000..f7a5575 --- /dev/null +++ b/packs/essentiels/Code/ecrire-un-test-pour-cette-fonction.md @@ -0,0 +1,30 @@ +--- +id: 747677e2-8379-4e93-b760-64711fb43331 +title: Écrire un test pour cette fonction +tags: [code, tests, qualité] +targets: [défaut, claude, chatgpt, qwen, deepseek] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: langage + label: Langage du code + type: enum + choices: [Python, JavaScript, TypeScript, AutoHotkey, PHP, autre] + default: Python + - name: type_test + label: Type de test + type: enum + choices: [unitaire, intégration, cas limites] + default: unitaire +--- + +Écris un test {{type_test}} pour la fonction {{langage}} ci-dessous. + +- Utilise le framework de test usuel du langage, sans l'importer si ce n'est pas nécessaire à la lisibilité. +- Couvre le cas nominal et, selon le type choisi, les entrées vides, nulles ou aux bornes. +- Noms de tests explicites ; un seul comportement vérifié par test. +- Donne uniquement le code du test, précédé d'une ligne de commentaire indiquant où le placer. + +Fonction : +{{selection}} diff --git a/packs/essentiels/Code/expliquer-ce-code.md b/packs/essentiels/Code/expliquer-ce-code.md new file mode 100644 index 0000000..ff25e10 --- /dev/null +++ b/packs/essentiels/Code/expliquer-ce-code.md @@ -0,0 +1,25 @@ +--- +id: e0b91c9f-557f-4da1-8d48-734844a61102 +title: Expliquer ce code +tags: [code, explication, pédagogie] +targets: [défaut, claude, chatgpt, qwen, deepseek] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: detail + label: Niveau de détail + type: enum + choices: [par blocs, ligne par ligne, résumé puis détail] + default: par blocs +--- + +Explique le code ci-dessous (détail demandé : {{detail}}). + +- Commence par ce que le code fait, en une phrase. +- Puis explique chaque partie dans l'ordre, en nommant le rôle des variables importantes. +- Signale les effets de bord, les hypothèses implicites et les points fragiles. +- Termine par un exemple d'entrée et le résultat attendu. + +Code : +{{selection}} diff --git a/packs/essentiels/Code/message-de-commit-a-partir-du-diff.md b/packs/essentiels/Code/message-de-commit-a-partir-du-diff.md new file mode 100644 index 0000000..6c64c95 --- /dev/null +++ b/packs/essentiels/Code/message-de-commit-a-partir-du-diff.md @@ -0,0 +1,25 @@ +--- +id: 992e31fa-6259-4716-b968-d0845f7f091a +title: Message de commit à partir du diff +tags: [git, commit, diff] +targets: [défaut, claude, chatgpt, qwen, deepseek] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: style + label: Style du message + type: enum + choices: [conventionnel, simple, détaillé] + default: conventionnel +--- + +À partir du diff ci-dessous (presse-papiers), écris un message de commit (style : {{style}}). + +- Style « conventionnel » = préfixe de type (`feat:`, `fix:`, `docs:`…) suivi d'un résumé à l'impératif, 72 caractères au maximum. +- Style « détaillé » = résumé, ligne vide, puis 2 à 4 puces expliquant le pourquoi des changements. +- Décris l'intention du changement, pas la liste mécanique des fichiers. +- Donne uniquement le message, sans guillemets ni bloc de code. + +Diff : +{{clipboard}} diff --git a/packs/essentiels/Code/revue-de-code.md b/packs/essentiels/Code/revue-de-code.md new file mode 100644 index 0000000..42e570f --- /dev/null +++ b/packs/essentiels/Code/revue-de-code.md @@ -0,0 +1,30 @@ +--- +id: 87aa26f5-ad2b-497e-b382-0b49bb50cd3c +title: Revue de code +tags: [code, revue, qualité] +targets: [défaut, claude, chatgpt, qwen, deepseek] +favorite: true +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: langage + label: Langage du code + type: enum + choices: [Python, JavaScript, TypeScript, AutoHotkey, PHP, autre] + default: Python + - name: niveau + label: Niveau de sévérité + type: enum + choices: [rapide, standard, strict] + default: standard +--- + +Tu es un relecteur de code senior. Relis le code {{langage}} suivant (niveau de revue : {{niveau}}) et signale, par ordre de gravité : +1. les bogues probables et les cas limites non gérés ; +2. les risques de sécurité ou de perte de données ; +3. les problèmes de lisibilité qui gênent la maintenance. + +Pour chaque point : explique le problème en une phrase et propose la correction minimale. Termine par un verdict en une ligne : prêt, à corriger, ou à retravailler. Ne réécris pas tout le code. + +Code : +{{selection}} diff --git a/packs/essentiels/Général/expliquer-simplement.md b/packs/essentiels/Général/expliquer-simplement.md new file mode 100644 index 0000000..d42bc22 --- /dev/null +++ b/packs/essentiels/Général/expliquer-simplement.md @@ -0,0 +1,26 @@ +--- +id: 8d5b0278-9098-41af-b964-f0ef95b003b4 +title: Expliquer simplement +tags: [explication, vulgarisation] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: public + label: Public visé + type: enum + choices: [grand public, débutant, connaisseur] + default: grand public +--- + +Explique simplement le sujet ou le texte suivant, pour un public « {{public}} ». + +- Commence par l'idée essentielle en une phrase. +- Utilise un exemple concret de la vie courante. +- Évite le jargon ; quand un terme technique est indispensable, définis-le en quelques mots. +- Termine par un résumé en trois points au maximum. +- Réponds en français. + +Sujet ou texte à expliquer : +{{selection}} diff --git a/packs/essentiels/Général/expliquer-une-erreur.md b/packs/essentiels/Général/expliquer-une-erreur.md new file mode 100644 index 0000000..e5009cf --- /dev/null +++ b/packs/essentiels/Général/expliquer-une-erreur.md @@ -0,0 +1,23 @@ +--- +id: 9da30617-2cc8-4b1d-94ad-2fd92030ee9c +title: Expliquer une erreur +tags: [débogage, erreur, pédagogie] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: niveau + label: Mon niveau sur ce sujet + type: enum + choices: [débutant, intermédiaire, avancé] + default: intermédiaire +--- + +Explique l'erreur ci-dessous à un niveau {{niveau}} : +1. ce qu'elle signifie, en une phrase simple ; +2. la cause la plus probable dans ce contexte ; +3. la correction minimale à appliquer, avec l'exemple corrigé seulement. + +Message d'erreur et contexte : +{{selection}} diff --git a/packs/essentiels/Général/plan-action-a-partir-d-un-texte.md b/packs/essentiels/Général/plan-action-a-partir-d-un-texte.md new file mode 100644 index 0000000..8cedcb5 --- /dev/null +++ b/packs/essentiels/Général/plan-action-a-partir-d-un-texte.md @@ -0,0 +1,28 @@ +--- +id: 3a8e4180-1cbe-4910-b740-2d62215f88e6 +title: Plan d'action à partir d'un texte +tags: [plan, actions, organisation] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: horizon + label: Horizon du plan + type: enum + choices: [aujourd'hui, cette semaine, ce mois] + default: cette semaine +--- + +À partir du texte ci-dessous, construis un plan d'action concret pour l'horizon « {{horizon}} ». + +Présente-le ainsi : +1. **Objectif** — en une phrase. +2. **Étapes** — liste numérotée, chaque étape commence par un verbe d'action et tient en une ligne. +3. **Priorités** — classe les étapes en « indispensable » et « si le temps le permet ». +4. **Points à clarifier** — ce que le texte ne dit pas et qui bloque une étape. + +N'invente ni échéance ni responsable qui ne figurent pas dans le texte. + +Texte : +{{selection}} diff --git a/packs/essentiels/Général/questions-de-clarification.md b/packs/essentiels/Général/questions-de-clarification.md new file mode 100644 index 0000000..28808be --- /dev/null +++ b/packs/essentiels/Général/questions-de-clarification.md @@ -0,0 +1,24 @@ +--- +id: 4d799fba-15c0-40b7-b46a-761a373d3fa2 +title: Questions de clarification +tags: [clarification, précision, cadrage] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: nombre + label: Nombre de questions + type: enum + choices: ["3", "5", "10"] + default: "5" +--- + +Avant de répondre à la demande ci-dessous, pose exactement {{nombre}} questions de clarification. + +- Choisis les questions dont la réponse changera le plus le résultat. +- Formule chaque question de façon courte, avec si possible des choix suggérés entre parenthèses. +- Ne réponds pas encore à la demande : termine en invitant à répondre aux questions. + +Demande : +{{selection}} diff --git a/packs/essentiels/Général/resumer-en-5-points.md b/packs/essentiels/Général/resumer-en-5-points.md new file mode 100644 index 0000000..2998482 --- /dev/null +++ b/packs/essentiels/Général/resumer-en-5-points.md @@ -0,0 +1,28 @@ +--- +id: 96d0d5b7-7a07-43a1-af84-dfa7024b4bcf +title: Résumer en 5 points +tags: [résumé, synthèse, court] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: true +pinned: 1 +hotstring: resu; +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: ton + label: Ton de la réponse + type: enum + choices: [neutre, formel, amical, direct] + default: neutre + - name: longueur + label: Longueur max + type: enum + choices: ["5 points", "1 paragraphe", "détaillé"] + default: "5 points" +--- + +Résume le texte suivant en {{longueur}}, ton {{ton}}. +Réponds en français. Conserve les faits, les chiffres et les noms propres ; ne reformule pas la première phrase et n'ajoute aucune information absente du texte. + +Texte : +{{selection}} diff --git a/packs/essentiels/Écriture/corriger-sans-changer-le-style.md b/packs/essentiels/Écriture/corriger-sans-changer-le-style.md new file mode 100644 index 0000000..e3053c8 --- /dev/null +++ b/packs/essentiels/Écriture/corriger-sans-changer-le-style.md @@ -0,0 +1,18 @@ +--- +id: 3cf4a9c9-caf6-4143-ab7c-5c8e098fc3dd +title: Corriger sans changer le style +tags: [correction, orthographe, style] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +--- + +Corrige le texte suivant : orthographe, grammaire, ponctuation et fautes de frappe uniquement. + +- Ne change ni le style, ni le ton, ni le vocabulaire, ni l'ordre des phrases. +- Conserve la mise en forme (paragraphes, listes, sauts de ligne). +- Donne uniquement le texte corrigé, sans explication ; si tu hésites sur une correction, laisse le texte d'origine. + +Texte : +{{selection}} diff --git a/packs/essentiels/Écriture/courriel-poli-a-partir-de-notes-brutes.md b/packs/essentiels/Écriture/courriel-poli-a-partir-de-notes-brutes.md new file mode 100644 index 0000000..2e3a0d3 --- /dev/null +++ b/packs/essentiels/Écriture/courriel-poli-a-partir-de-notes-brutes.md @@ -0,0 +1,29 @@ +--- +id: fe9eb1b8-64d4-4930-bc78-0271f37463ed +title: Courriel poli à partir de notes brutes +tags: [courriel, notes, rédaction] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: destinataire + label: Destinataire + type: text + default: la personne concernée + - name: ton + label: Ton du courriel + type: enum + choices: [poli, formel, amical professionnel] + default: poli +--- + +Transforme les notes brutes ci-dessous en un courriel prêt à envoyer à {{destinataire}}, avec un ton {{ton}}. + +- Propose d'abord une ligne « Objet : … » courte et explicite. +- Formules de politesse adaptées au ton, paragraphes courts, demande ou information principale dès le début. +- N'invente aucun fait, chiffre ou engagement absent des notes. +- Termine par une formule de conclusion simple, sans signature. + +Notes brutes : +{{selection}} diff --git a/packs/essentiels/Écriture/raccourcir-de-moitie.md b/packs/essentiels/Écriture/raccourcir-de-moitie.md new file mode 100644 index 0000000..7040c11 --- /dev/null +++ b/packs/essentiels/Écriture/raccourcir-de-moitie.md @@ -0,0 +1,25 @@ +--- +id: 655bf6aa-4181-4ee5-89b4-85861ea7db23 +title: Raccourcir de moitié +tags: [raccourcir, concision, synthèse] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: priorite + label: À conserver en priorité + type: enum + choices: [idées essentielles, chiffres et faits, ton original] + default: idées essentielles +--- + +Raccourcis le texte suivant d'environ moitié, en conservant en priorité : {{priorite}}. + +- Supprime les répétitions, les détours et les formules vides. +- Ne supprime aucune information indispensable à la compréhension. +- Conserve la structure générale et le ton. +- Donne uniquement le texte raccourci. + +Texte : +{{selection}} diff --git a/packs/essentiels/Écriture/reformuler.md b/packs/essentiels/Écriture/reformuler.md new file mode 100644 index 0000000..36441ba --- /dev/null +++ b/packs/essentiels/Écriture/reformuler.md @@ -0,0 +1,24 @@ +--- +id: 2f103340-ed13-4e83-8f62-3e874caa75da +title: Reformuler +tags: [reformulation, réécriture, style] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: true +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: ton + label: Ton souhaité + type: enum + choices: [neutre, formel, amical, direct] + default: neutre +--- + +Reformule le texte suivant avec un ton {{ton}}. + +- Conserve exactement le sens, les faits et les chiffres. +- Améliore la fluidité et corrige les lourdeurs, sans raccourcir ni allonger sensiblement. +- Donne une seule version, sans commentaires ni variantes. + +Texte : +{{selection}} diff --git a/packs/essentiels/Écriture/traduire-fr-en.md b/packs/essentiels/Écriture/traduire-fr-en.md new file mode 100644 index 0000000..d5bf527 --- /dev/null +++ b/packs/essentiels/Écriture/traduire-fr-en.md @@ -0,0 +1,29 @@ +--- +id: 802ebc80-4634-49b9-b438-e7489dadb5fc +title: Traduire FR-EN +tags: [traduction, anglais, français] +targets: [défaut, claude, chatgpt, gemini, qwen] +favorite: false +created: 2026-10-11 +updated: 2026-10-11 +variables: + - name: direction + label: Sens de traduction + type: enum + choices: [FR vers EN, EN vers FR] + default: FR vers EN + - name: registre + label: Registre + type: enum + choices: [neutre, formel, familier] + default: neutre +--- + +Traduis le texte suivant (sens : {{direction}}), registre {{registre}}. + +- Conserve exactement la mise en forme : titres, listes, gras, liens et sauts de ligne. +- Ne traduis pas les noms propres ni les termes techniques consacrés dans la langue d'arrivée. +- Donne uniquement la traduction, sans commentaire. + +Texte : +{{selection}} diff --git a/scripts/check_pack.py b/scripts/check_pack.py new file mode 100644 index 0000000..e0fe2f6 --- /dev/null +++ b/scripts/check_pack.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Contrôle de cohérence du projet PromptKey (utilisé par la CI Gitea Actions). + +Vérifie : + 1. la présence des fichiers attendus à la racine du dépôt ; + 2. le pack de démarrage : 20 prompts, 5 par catégorie (Général, Écriture, + Code, Analyse) sous packs/essentiels/ ; + 3. chaque prompt : en-tête délimité par ---, contenant au minimum `id` + (GUID valide et unique) et `title` ; + 4. les noms de fichiers : minuscules, tirets, sans accents ; + 5. toute variable {{...}} du corps est soit automatique (selection, + clipboard, date, datetime, time, app, cursor), soit déclarée dans le + bloc `variables:` de l'en-tête. + +Usage : python3 scripts/check_pack.py [racine_du_depot] +Code de sortie : 0 si tout est conforme, 1 sinon (liste des erreurs). +""" + +import re +import sys +import uuid +from pathlib import Path + +RACINE = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent + +FICHIERS_ATTENDUS = ["README.md", "CHANGELOG.md", "LICENSE", ".gitignore", "build.ps1"] + +CATEGORIES = {"Général": 5, "Écriture": 5, "Code": 5, "Analyse": 5} + +VARIABLES_AUTO = {"selection", "clipboard", "date", "datetime", "time", "app", "cursor"} + +MOTIF_NOM_FICHIER = re.compile(r"^[a-z0-9]+(-[a-z0-9]+)*\.md$") +MOTIF_VARIABLE = re.compile(r"\{\{\s*([A-Za-z0-9_]+)\s*\}\}") + +erreurs = [] + + +def erreur(message): + erreurs.append(message) + print(f"ERREUR : {message}") + + +# --- 1. Fichiers attendus --------------------------------------------------- +for nom in FICHIERS_ATTENDUS: + if not (RACINE / nom).is_file(): + erreur(f"fichier attendu manquant : {nom}") + +# --- 2 à 5. Pack de démarrage ------------------------------------------------ +pack = RACINE / "packs" / "essentiels" +if not pack.is_dir(): + erreur(f"dossier du pack introuvable : {pack}") +else: + ids_vus = {} + total = 0 + for categorie, attendu in CATEGORIES.items(): + dossier = pack / categorie + fichiers = sorted(dossier.glob("*.md")) if dossier.is_dir() else [] + if len(fichiers) != attendu: + erreur(f"catégorie {categorie} : {len(fichiers)} prompt(s), {attendu} attendus") + for fichier in fichiers: + total += 1 + rel = fichier.relative_to(RACINE) + if not MOTIF_NOM_FICHIER.match(fichier.name): + erreur(f"{rel} : nom de fichier non conforme (minuscules, tirets, sans accents)") + + texte = fichier.read_text(encoding="utf-8") + lignes = texte.splitlines() + if not lignes or lignes[0].strip() != "---" or "---" not in lignes[1:]: + erreur(f"{rel} : en-tête --- absent ou non fermé") + continue + fin = lignes[1:].index("---") + 1 + entete = lignes[1:fin] + corps = "\n".join(lignes[fin + 1:]) + + champs = {} + declarees = set() + for ligne in entete: + m = re.match(r"^([a-z_]+):\s*(.*)$", ligne) + if m: + champs[m.group(1)] = m.group(2).strip() + m2 = re.match(r"^\s*- name:\s*(\S+)\s*$", ligne) + if m2: + declarees.add(m2.group(1).lower()) + + for champ in ("id", "title"): + if not champs.get(champ): + erreur(f"{rel} : champ obligatoire absent dans l'en-tête : {champ}") + + guid = champs.get("id", "") + try: + uuid.UUID(guid) + except (ValueError, AttributeError): + erreur(f"{rel} : id n'est pas un GUID valide : {guid!r}") + if guid in ids_vus: + erreur(f"{rel} : id dupliqué (déjà dans {ids_vus[guid]})") + ids_vus[guid] = str(rel) + + for nom_var in MOTIF_VARIABLE.findall(corps): + if nom_var.lower() not in VARIABLES_AUTO and nom_var.lower() not in declarees: + erreur(f"{rel} : variable {{{{{nom_var}}}}} utilisée mais non déclarée") + + if total != 20: + erreur(f"pack : {total} prompt(s) au total, 20 attendus") + else: + print(f"Pack : {total} prompts, {len(ids_vus)} id uniques.") + +if erreurs: + print(f"\nÉCHEC : {len(erreurs)} erreur(s).") + sys.exit(1) +print("OK : fichiers attendus présents, pack cohérent.") diff --git a/tests/run_tests.ahk b/tests/run_tests.ahk new file mode 100644 index 0000000..ecc8a84 --- /dev/null +++ b/tests/run_tests.ahk @@ -0,0 +1,137 @@ +#Requires AutoHotkey v2.0 +; Tests autonomes de PromptKey (architecture §22) — à lancer sous Windows : +; AutoHotkey64.exe tests\run_tests.ahk +; Sortie : lignes OK / ÉCHEC sur la console (ou dans le fichier passé en premier argument), +; code de sortie 0 si tout passe, 1 sinon. Aucune GUI, aucune injection. +#SingleInstance Off + +#Include ..\lib\Util.ahk +#Include ..\lib\Json.ahk +#Include ..\lib\Frontmatter.ahk +#Include ..\lib\VariableEngine.ahk +#Include ..\lib\PromptStore.ahk +#Include ..\lib\Search.ahk + +global failures := 0 +global outFile := A_Args.Length > 0 ? A_Args[1] : "" + +Report(ok, name, detail := "") { + global failures, outFile + if (!ok) + failures++ + line := (ok ? "OK " : "ÉCHEC") " " name (detail != "" ? " — " detail : "") "`n" + if (outFile != "") + FileAppend(line, outFile, "UTF-8") + else + FileAppend(line, "*") +} + +Check(cond, name, detail := "") { + Report(cond, name, detail) +} + +; ---------- Util : normalisation ---------- +Check(Util.Normalize("Résumé ÉÉ") = "resume ee", "Normalize accents") +Check(Util.Slugify("Revue de code !") = "revue-de-code", "Slugify") + +; ---------- Frontmatter : analyse ---------- +sample := " +( +--- +id: test-id-1 +title: Résumer en 5 points +tags: [résumé, synthèse, court] +favorite: true +pinned: 2 +variables: + - name: ton + label: Ton de la réponse + type: enum + choices: [neutre, formel, amical] + default: neutre +--- + +Résume avec un ton {{ton}}. + +Texte : +{{selection}} +)" +parsed := Frontmatter.Parse(sample) +Check(parsed["errors"].Length = 0, "Frontmatter sans erreur", parsed["errors"].Length ? parsed["errors"][1] : "") +Check(parsed["meta"]["title"] = "Résumer en 5 points", "Frontmatter title") +Check(parsed["meta"]["tags"].Length = 3, "Frontmatter tags (3)") +Check(parsed["meta"]["favorite"] = 1, "Frontmatter favorite booléen") +Check(parsed["meta"]["variables"].Length = 1, "Frontmatter 1 variable") +Check(parsed["meta"]["variables"][1]["choices"].Length = 3, "Frontmatter choices (3)") +Check(InStr(parsed["body"], "{{selection}}") > 0, "Frontmatter corps conservé") + +; Fichier sans en-tête : accepté, corps = tout. +plain := Frontmatter.Parse("Juste un texte.") +Check(plain["hasHeader"] = false && plain["body"] = "Juste un texte.", "Sans en-tête accepté") + +; Erreur : en-tête jamais fermé. +bad := Frontmatter.Parse("---`nid: x`ntitle: y`ncorps sans fermeture") +Check(bad["errors"].Length > 0, "En-tête non fermé = erreur signalée") + +; Aller-retour sérialisation. +item := PromptItem("test.md", "Général", parsed) +ser := Frontmatter.Serialize(item.Meta, item.Body) +reparsed := Frontmatter.Parse(ser) +Check(reparsed["errors"].Length = 0 && reparsed["meta"]["title"] = "Résumer en 5 points", "Aller-retour Serialize/Parse") + +; ---------- Variables ---------- +ctx := Map("selection", "TEXTE SÉLECTIONNÉ", "clipboardText", "PRESSE", "process", "chrome.exe") +defs := VariableEngine.ManualVars(item) +Check(defs.Length = 1 && defs[1]["name"] = "ton", "ManualVars : ton seulement (selection est automatique)") +resolved := VariableEngine.Resolve(item.Body, Map("ton", "formel"), ctx) +Check(InStr(resolved["text"], "ton formel") > 0, "Resolve remplace {{ton}}") +Check(InStr(resolved["text"], "TEXTE SÉLECTIONNÉ") > 0, "Resolve remplace {{selection}}") +Check(resolved["unresolved"].Length = 0, "Resolve sans non-résolue") +withCursor := VariableEngine.Resolve("Début {{cursor}} fin", Map(), ctx) +Check(withCursor["cursorFromEnd"] = 4 && withCursor["text"] = "Début fin", "Marqueur cursor : position et retrait") + +; ---------- JSON ---------- +obj := Json.Parse('{"a": 1, "b": [1, 2, "trois"], "c": {"d": "x\ny"}}') +Check(obj["a"] = 1 && obj["b"].Length = 3 && obj["c"]["d"] = "x`ny", "JSON parse imbriqué") +round := Json.Parse(Json.Stringify(obj)) +Check(round["b"][3] = "trois", "JSON aller-retour") + +; ---------- Recherche ---------- +class FakePrompt { + __New(title, cat, tags, body) { + this.Id := title + this.Title := title + this.Category := cat + this.Tags := tags + this.Body := body + this.Pinned := 0 + this.Favorite := 0 + } +} +prompts := [ + FakePrompt("Résumer en 5 points", "Général", ["résumé"], "Résume le texte"), + FakePrompt("Revue de code", "Code", ["code"], "Relis ce code"), + FakePrompt("Traduire en anglais", "Écriture", ["traduction"], "Traduis")] +found := Search.Filter(prompts, "resu", Map()) +Check(found.Length = 1 && found[1].Title = "Résumer en 5 points", "Recherche 'resu' trouve 'Résumer' (accents)") +found2 := Search.Filter(prompts, "code", Map()) +Check(found2.Length >= 1 && found2[1].Title = "Revue de code", "Recherche 'code' classe la revue en premier") +all := Search.Filter(prompts, "", Map()) +Check(all.Length = 3, "Recherche vide : toute la bibliothèque") + +; ---------- Encodage : aller-retour UTF-8 bit à bit (§19.4) ---------- +tmpFile := A_Temp "\promptkey-test-encodage.md" +tricky := "é à — ‘’ « » 😀`r`n{{variable}} deuxième ligne" +f := FileOpen(tmpFile, "w", "UTF-8") +f.Write(tricky) +f.Close() +back := FileRead(tmpFile, "UTF-8") +Check(back = tricky, "UTF-8 aller-retour identique") +FileDelete(tmpFile) + +; ---------- Bilan ---------- +if (outFile != "") + FileAppend("----`n" (failures = 0 ? "TOUS LES TESTS PASSENT" : failures " ÉCHEC(S)") "`n", outFile, "UTF-8") +else + FileAppend("----`n" (failures = 0 ? "TOUS LES TESTS PASSENT" : failures " ÉCHEC(S)") "`n", "*") +ExitApp(failures = 0 ? 0 : 1)