feat(pack,docs): pack de démarrage 20 prompts, README, tests, CI, build
ci / verifier-pack (push) Successful in 1m29s
ci / verifier-pack (push) Successful in 1m29s
- packs/essentiels : 20 prompts en 4 catégories (Général, Écriture, Code, Analyse), en-têtes conformes au parseur, variables déclarées = utilisées - tests/run_tests.ahk : suites Frontmatter, Variables, JSON, Recherche, encodage UTF-8 (à exécuter sous Windows avec AutoHotkey v2) - scripts/check_pack.py + .gitea/workflows/ci.yml : cohérence du pack - build.ps1 : tests puis compilation Ahk2Exe et zip portable - docs : document d'architecture et de conception v1.0
This commit is contained in:
@@ -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.")
|
||||
Reference in New Issue
Block a user