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
112 lines
4.2 KiB
Python
112 lines
4.2 KiB
Python
#!/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.")
|