phi-complexity 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,65 @@
1
+ """
2
+ __init__.py — API publique de phi-complexity.
3
+ Expose les fonctions de haut niveau pour une utilisation simple.
4
+ """
5
+ from .core import PHI, TAXE_SUTURE, ETA_GOLDEN, VERSION, AUTEUR, FRAMEWORK
6
+ from .analyseur import AnalyseurPhi
7
+ from .metriques import CalculateurRadiance
8
+ from .rapport import GenerateurRapport
9
+
10
+
11
+ def auditer(fichier: str) -> dict:
12
+ """
13
+ Lance un audit complet sur un fichier Python.
14
+ Retourne un dictionnaire de métriques.
15
+
16
+ Usage:
17
+ from phi_complexity import auditer
18
+ result = auditer("mon_script.py")
19
+ print(result["radiance"]) # → 82.4
20
+ """
21
+ analyseur = AnalyseurPhi(fichier)
22
+ resultat = analyseur.analyser()
23
+ calculateur = CalculateurRadiance(resultat)
24
+ return calculateur.calculer()
25
+
26
+
27
+ def rapport_console(fichier: str) -> str:
28
+ """Retourne le rapport ASCII formaté pour le terminal."""
29
+ metriques = auditer(fichier)
30
+ return GenerateurRapport(metriques).console()
31
+
32
+
33
+ def rapport_markdown(fichier: str, sortie: str = None) -> str:
34
+ """
35
+ Génère un rapport Markdown.
36
+ Si `sortie` est spécifié, sauvegarde dans ce fichier.
37
+ Retourne le contenu Markdown.
38
+ """
39
+ metriques = auditer(fichier)
40
+ gen = GenerateurRapport(metriques)
41
+ if sortie:
42
+ gen.sauvegarder_markdown(sortie)
43
+ return gen.markdown()
44
+
45
+
46
+ def rapport_json(fichier: str) -> str:
47
+ """Retourne le rapport JSON pour CI/CD."""
48
+ metriques = auditer(fichier)
49
+ return GenerateurRapport(metriques).json()
50
+
51
+
52
+ __version__ = VERSION
53
+ __author__ = AUTEUR
54
+ __all__ = [
55
+ "auditer",
56
+ "rapport_console",
57
+ "rapport_markdown",
58
+ "rapport_json",
59
+ "PHI",
60
+ "TAXE_SUTURE",
61
+ "ETA_GOLDEN",
62
+ "AnalyseurPhi",
63
+ "CalculateurRadiance",
64
+ "GenerateurRapport",
65
+ ]
@@ -0,0 +1,3 @@
1
+ """Point d'entrée pour python -m phi_complexity"""
2
+ from phi_complexity.cli import main
3
+ main()
@@ -0,0 +1,281 @@
1
+ """
2
+ analyseur.py — Dissection fractale du code Python via AST.
3
+ Suturée selon les recommandations de phi-complexity v0.1.0 (Protocole BMAD).
4
+ _analyser_micro() décomposée en 4 règles hermétiques — Cycles 13:20 → 33:33.
5
+ """
6
+ import ast
7
+ from dataclasses import dataclass, field
8
+ from typing import List, Optional
9
+
10
+ from .core import fibonacci_plus_proche, distance_fibonacci
11
+
12
+
13
+ # ────────────────────────────────────────────────────────
14
+ # STRUCTURES DE DONNÉES (dataclasses immuables)
15
+ # ────────────────────────────────────────────────────────
16
+
17
+ @dataclass
18
+ class MetriqueFonction:
19
+ """Représente une fonction analysée avec toutes ses métriques brutes."""
20
+ nom: str
21
+ ligne: int
22
+ complexite: int # Nombre de nœuds AST (pression morphique)
23
+ nb_args: int # Nombre d'arguments
24
+ nb_lignes: int # Longueur en lignes
25
+ profondeur_max: int # Imbrication maximale
26
+ distance_fib: float # Éloignement de la séquence naturelle
27
+ phi_ratio: float # Rapport complexité/moyenne (idéal: φ)
28
+
29
+
30
+ @dataclass
31
+ class Annotation:
32
+ """Une observation chirurgicale sur une ligne spécifique du code."""
33
+ ligne: int
34
+ message: str
35
+ niveau: str # 'INFO', 'WARNING', 'CRITICAL'
36
+ extrait: str # La ligne de code concernée
37
+ categorie: str # 'LILITH', 'SUTURE', 'SOUVERAINETE', 'FIBONACCI'
38
+
39
+
40
+ @dataclass
41
+ class ResultatAnalyse:
42
+ """Contient tous les résultats bruts d'une analyse de fichier."""
43
+ fichier: str
44
+ fonctions: List[MetriqueFonction] = field(default_factory=list)
45
+ annotations: List[Annotation] = field(default_factory=list)
46
+ nb_classes: int = 0
47
+ nb_imports: int = 0
48
+ nb_lignes_total: int = 0
49
+ nb_commentaires: int = 0
50
+ oudjat: Optional[MetriqueFonction] = None
51
+
52
+
53
+ # ────────────────────────────────────────────────────────
54
+ # ANALYSEUR PRINCIPAL
55
+ # ────────────────────────────────────────────────────────
56
+
57
+ class AnalyseurPhi:
58
+ """
59
+ Analyseur fractal basé sur AST.
60
+ Dissèque le code Python pour extraire ses métriques souveraines.
61
+ """
62
+
63
+ def __init__(self, fichier: str):
64
+ self.fichier = fichier
65
+ self.tree: Optional[ast.AST] = None
66
+ self.lignes: List[str] = []
67
+ self.resultat = ResultatAnalyse(fichier=fichier)
68
+
69
+ def charger(self) -> "AnalyseurPhi":
70
+ """Charge et parse le fichier Python avec gestionnaire de contexte (Règle II)."""
71
+ with open(self.fichier, "r", encoding="utf-8") as f:
72
+ contenu = f.read()
73
+ self.lignes = contenu.splitlines()
74
+ self.tree = ast.parse(contenu, filename=self.fichier)
75
+ self._injecter_parents()
76
+ return self
77
+
78
+ def analyser(self) -> ResultatAnalyse:
79
+ """Lance l'analyse complète. Orchestre — ne calcule pas directement."""
80
+ if self.tree is None:
81
+ self.charger()
82
+ self.resultat.nb_lignes_total = len(self.lignes)
83
+ self._compter_elements_globaux()
84
+ self._analyser_fonctions()
85
+ self._appliquer_regles_souveraines()
86
+ self._identifier_oudjat()
87
+ return self.resultat
88
+
89
+ # ────────────────────────────────────────────────────────
90
+ # PRÉPARATION DE L'ARBRE AST
91
+ # ────────────────────────────────────────────────────────
92
+
93
+ def _injecter_parents(self):
94
+ """Injecte les références parent dans l'arbre AST pour la remontée."""
95
+ for node in ast.walk(self.tree):
96
+ for child in ast.iter_child_nodes(node):
97
+ child.parent = node
98
+
99
+ def _compter_elements_globaux(self):
100
+ """Compte classes, imports et commentaires (éléments macro)."""
101
+ for node in ast.walk(self.tree):
102
+ if isinstance(node, ast.ClassDef):
103
+ self.resultat.nb_classes += 1
104
+ elif isinstance(node, (ast.Import, ast.ImportFrom)):
105
+ self.resultat.nb_imports += 1
106
+ self.resultat.nb_commentaires = sum(
107
+ 1 for ligne in self.lignes if ligne.strip().startswith("#")
108
+ )
109
+
110
+ # ────────────────────────────────────────────────────────
111
+ # ANALYSE DES FONCTIONS
112
+ # ────────────────────────────────────────────────────────
113
+
114
+ def _analyser_fonctions(self):
115
+ """Extrait les métriques de chaque fonction définie dans le fichier."""
116
+ for node in ast.walk(self.tree):
117
+ if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
118
+ metrique = self._mesurer_fonction(node)
119
+ self.resultat.fonctions.append(metrique)
120
+
121
+ def _mesurer_fonction(self, node: ast.AST) -> MetriqueFonction:
122
+ """Calcule toutes les métriques d'une seule fonction."""
123
+ end_line = getattr(node, "end_lineno", node.lineno)
124
+ nb_lignes = end_line - node.lineno + 1
125
+ return MetriqueFonction(
126
+ nom=node.name,
127
+ ligne=node.lineno,
128
+ complexite=len(list(ast.walk(node))),
129
+ nb_args=len(node.args.args),
130
+ nb_lignes=nb_lignes,
131
+ profondeur_max=self._profondeur_imbrication(node),
132
+ distance_fib=distance_fibonacci(nb_lignes),
133
+ phi_ratio=1.0, # Calculé après, quand la moyenne est connue
134
+ )
135
+
136
+ def _identifier_oudjat(self):
137
+ """Identifie la fonction dominante et calcule les φ-ratios."""
138
+ if not self.resultat.fonctions:
139
+ return
140
+ self.resultat.oudjat = max(
141
+ self.resultat.fonctions, key=lambda f: f.complexite
142
+ )
143
+ self._calculer_phi_ratios()
144
+
145
+ def _calculer_phi_ratios(self):
146
+ """Normalise la complexité de chaque fonction par la moyenne."""
147
+ moyenne = sum(f.complexite for f in self.resultat.fonctions) / len(
148
+ self.resultat.fonctions
149
+ )
150
+ if moyenne == 0:
151
+ return
152
+ for f in self.resultat.fonctions:
153
+ f.phi_ratio = f.complexite / moyenne
154
+
155
+ # ────────────────────────────────────────────────────────
156
+ # RÈGLES DE CODAGE SOUVERAIN (4 règles hermétiques)
157
+ # ────────────────────────────────────────────────────────
158
+
159
+ def _appliquer_regles_souveraines(self):
160
+ """Applique les 4 règles souveraines à chaque nœud de l'AST."""
161
+ for node in ast.walk(self.tree):
162
+ self._regle_lilith(node)
163
+ self._regle_raii(node)
164
+ self._regle_fibonacci(node)
165
+ self._regle_hermeticite(node)
166
+
167
+ def _regle_lilith(self, node: ast.AST):
168
+ """Règle I — Nœuds d'Entropie : détecte les boucles trop imbriquées."""
169
+ if not isinstance(node, (ast.For, ast.While)):
170
+ return
171
+ depth = self._profondeur_noeud(node)
172
+ if depth >= 2:
173
+ self._annoter(
174
+ node.lineno,
175
+ f"LILITH : Boucle imbriquée (profondeur {depth}). "
176
+ "La variance s'accumule — envisagez une fonction auxiliaire.",
177
+ "CRITICAL" if depth >= 3 else "WARNING",
178
+ "LILITH"
179
+ )
180
+
181
+ def _regle_raii(self, node: ast.AST):
182
+ """Règle II — Intégrité du Cycle de Vie : open() exige un gestionnaire."""
183
+ if not isinstance(node, ast.Call):
184
+ return
185
+ if not self._est_appel_open(node):
186
+ return
187
+ parent = getattr(node, "parent", None)
188
+ grand_parent = getattr(parent, "parent", None)
189
+ if isinstance(parent, ast.withitem) or isinstance(grand_parent, ast.With):
190
+ return
191
+ self._annoter(
192
+ node.lineno,
193
+ "SUTURE : 'open()' sans gestionnaire de contexte (with). "
194
+ "Risque de traînée d'entropie (fuite de ressource).",
195
+ "WARNING",
196
+ "SUTURE"
197
+ )
198
+
199
+ def _regle_fibonacci(self, node: ast.AST):
200
+ """Règle III — Taille Naturelle : les fonctions suivent Fibonacci."""
201
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
202
+ return
203
+ end_line = getattr(node, "end_lineno", node.lineno)
204
+ nb_lignes = end_line - node.lineno + 1
205
+ fib_proche = fibonacci_plus_proche(nb_lignes)
206
+ if nb_lignes > 55 and abs(nb_lignes - fib_proche) > 10:
207
+ self._annoter(
208
+ node.lineno,
209
+ f"FIBONACCI : '{node.name}' ({nb_lignes} lignes) s'éloigne "
210
+ f"de la séquence naturelle (idéal: {fib_proche}). "
211
+ "Scinder pour réduire la pression morphique.",
212
+ "WARNING",
213
+ "FIBONACCI"
214
+ )
215
+
216
+ def _regle_hermeticite(self, node: ast.AST):
217
+ """Règle IV — Herméticité : une fonction ne reçoit pas plus de 5 args."""
218
+ if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
219
+ return
220
+ nb_args = len(node.args.args)
221
+ if nb_args > 5:
222
+ self._annoter(
223
+ node.lineno,
224
+ f"SOUVERAINETÉ : '{node.name}' reçoit {nb_args} arguments. "
225
+ "Encapsuler dans un objet (max: 5 / idéal φ: 3).",
226
+ "INFO",
227
+ "SOUVERAINETE"
228
+ )
229
+
230
+ # ────────────────────────────────────────────────────────
231
+ # UTILITAIRES (fonctions pures, sans effets de bord)
232
+ # ────────────────────────────────────────────────────────
233
+
234
+ def _est_appel_open(self, node: ast.Call) -> bool:
235
+ """Retourne True si le nœud est un appel à open()."""
236
+ func = node.func
237
+ if isinstance(func, ast.Name):
238
+ return func.id == "open"
239
+ if isinstance(func, ast.Attribute):
240
+ return func.attr == "open"
241
+ return False
242
+
243
+ def _annoter(self, ligne: int, msg: str, niveau: str, categorie: str):
244
+ """Enregistre une annotation chirurgicale sur une ligne de code."""
245
+ extrait = self.lignes[ligne - 1].strip() if ligne <= len(self.lignes) else ""
246
+ self.resultat.annotations.append(
247
+ Annotation(ligne=ligne, message=msg, niveau=niveau,
248
+ extrait=extrait, categorie=categorie)
249
+ )
250
+
251
+ def _profondeur_imbrication(self, fn_node: ast.AST) -> int:
252
+ """
253
+ Profondeur max d'imbrication à l'intérieur d'une fonction.
254
+ Utilise une pile explicite (pas de récursion) — Règle III (Fibonacci).
255
+ """
256
+ _NOEUD_CONTROLE = (ast.For, ast.While, ast.If, ast.With)
257
+ pile = [(fn_node, 0)]
258
+ max_depth = 0
259
+ while pile:
260
+ noeud, depth = pile.pop()
261
+ est_controle = isinstance(noeud, _NOEUD_CONTROLE)
262
+ profondeur_courante = depth + 1 if est_controle else depth
263
+ max_depth = max(max_depth, profondeur_courante if est_controle else depth)
264
+ for child in ast.iter_child_nodes(noeud):
265
+ pile.append((child, profondeur_courante))
266
+ return max_depth
267
+
268
+ def _profondeur_noeud(self, node: ast.AST) -> int:
269
+ """Profondeur globale d'un nœud via remontée des parents injectés."""
270
+ _NOEUD_COMPTABLE = (ast.For, ast.While, ast.If, ast.FunctionDef)
271
+ ancetres = self._remonter_parents(node)
272
+ return sum(1 for a in ancetres if isinstance(a, _NOEUD_COMPTABLE))
273
+
274
+ def _remonter_parents(self, node: ast.AST) -> list:
275
+ """Retourne la liste des nœuds ancêtres en remontant l'arbre."""
276
+ ancetres = []
277
+ curr = getattr(node, "parent", None)
278
+ while curr is not None:
279
+ ancetres.append(curr)
280
+ curr = getattr(curr, "parent", None)
281
+ return ancetres
phi_complexity/cli.py ADDED
@@ -0,0 +1,155 @@
1
+ """
2
+ cli.py — Interface en ligne de commande souveraine.
3
+ Suturée selon les recommandations de phi-complexity v0.1.0 (Protocole BMAD).
4
+ main() décomposée en 5 fonctions hermétiques — Règle I : Herméticité de la Portée.
5
+ """
6
+ import sys
7
+ import os
8
+ import argparse
9
+
10
+ from . import auditer, rapport_console, rapport_markdown, rapport_json
11
+ from .core import VERSION
12
+
13
+
14
+ # ────────────────────────────────────────────────────────
15
+ # CONSTRUCTION DU PARSEUR (hermétique, sans effets de bord)
16
+ # ────────────────────────────────────────────────────────
17
+
18
+ def _construire_parseur() -> argparse.ArgumentParser:
19
+ """Construit et retourne le parseur d'arguments. Aucun état global."""
20
+ parser = argparse.ArgumentParser(
21
+ prog="phi",
22
+ description="phi-complexity — Audit de code par les invariants du nombre d'or (φ)",
23
+ formatter_class=argparse.RawDescriptionHelpFormatter,
24
+ epilog="""
25
+ Exemples :
26
+ phi check mon_script.py
27
+ phi check ./src/ --min-radiance 75
28
+ phi report mon_script.py --output rapport.md
29
+ phi check mon_script.py --format json
30
+ """
31
+ )
32
+ parser.add_argument("--version", action="version", version=f"phi-complexity {VERSION}")
33
+
34
+ subparsers = parser.add_subparsers(dest="commande")
35
+
36
+ check = subparsers.add_parser("check", help="Auditer un fichier ou un dossier")
37
+ check.add_argument("cible", help="Fichier .py ou dossier à auditer")
38
+ check.add_argument("--min-radiance", type=float, default=0,
39
+ help="Score minimum (exit code 1 si en-dessous)")
40
+ check.add_argument("--format", choices=["console", "json"], default="console",
41
+ help="Format de sortie")
42
+
43
+ report = subparsers.add_parser("report", help="Générer un rapport Markdown")
44
+ report.add_argument("cible", help="Fichier .py à analyser")
45
+ report.add_argument("--output", "-o", default=None,
46
+ help="Fichier de sortie (ex: rapport.md)")
47
+ return parser
48
+
49
+
50
+ # ────────────────────────────────────────────────────────
51
+ # COLLECTE DES FICHIERS (Suture des boucles LILITH)
52
+ # ────────────────────────────────────────────────────────
53
+
54
+ def _fichiers_depuis_dossier(dossier: str) -> list:
55
+ """Collecte récursivement les .py d'un dossier (boucles isolées)."""
56
+ fichiers = []
57
+ for racine, _, noms in os.walk(dossier):
58
+ fichiers.extend(
59
+ os.path.join(racine, nom)
60
+ for nom in noms
61
+ if nom.endswith(".py")
62
+ )
63
+ return sorted(fichiers)
64
+
65
+
66
+ def _collecter_fichiers(cible: str) -> list:
67
+ """Retourne la liste des fichiers Python à auditer depuis un chemin."""
68
+ if os.path.isfile(cible):
69
+ return [cible] if cible.endswith(".py") else []
70
+ if os.path.isdir(cible):
71
+ return _fichiers_depuis_dossier(cible)
72
+ return []
73
+
74
+
75
+ # ────────────────────────────────────────────────────────
76
+ # EXÉCUTION DES SOUS-COMMANDES (une fonction par rôle)
77
+ # ────────────────────────────────────────────────────────
78
+
79
+ def _executer_check(args: argparse.Namespace, fichiers: list) -> int:
80
+ """Exécute la sous-commande 'check'. Retourne le code de sortie."""
81
+ exit_code = 0
82
+ for fichier in fichiers:
83
+ exit_code = max(exit_code, _auditer_un_fichier(fichier, args))
84
+ return exit_code
85
+
86
+
87
+ def _auditer_un_fichier(fichier: str, args: argparse.Namespace) -> int:
88
+ """Audite un seul fichier et affiche le résultat. Retourne 0 ou 1."""
89
+ try:
90
+ if args.format == "json":
91
+ print(rapport_json(fichier))
92
+ else:
93
+ print(rapport_console(fichier))
94
+ print()
95
+
96
+ if args.min_radiance > 0:
97
+ metriques = auditer(fichier)
98
+ if metriques["radiance"] < args.min_radiance:
99
+ return 1
100
+ except SyntaxError as e:
101
+ print(f"⚠ Erreur de syntaxe dans {fichier}: {e}")
102
+ return 1
103
+ except Exception as e:
104
+ print(f"⚠ Erreur lors de l'analyse de {fichier}: {e}")
105
+ return 1
106
+ return 0
107
+
108
+
109
+ def _executer_report(args: argparse.Namespace, fichiers: list) -> int:
110
+ """Exécute la sous-commande 'report'. Retourne le code de sortie."""
111
+ for fichier in fichiers:
112
+ sortie = _nom_rapport(fichier, args.output)
113
+ try:
114
+ rapport_markdown(fichier, sortie=sortie)
115
+ print(f"✦ Rapport sauvegardé : {sortie}")
116
+ except Exception as e:
117
+ print(f"❌ Erreur : {e}")
118
+ return 1
119
+ return 0
120
+
121
+
122
+ def _nom_rapport(fichier: str, sortie_demandee: str) -> str:
123
+ """Calcule le nom du fichier rapport de sortie."""
124
+ if sortie_demandee:
125
+ return sortie_demandee
126
+ base = os.path.splitext(os.path.basename(fichier))[0]
127
+ return f"RAPPORT_PHI_{base}.md"
128
+
129
+
130
+ # ────────────────────────────────────────────────────────
131
+ # POINT D'ENTRÉE (hermétique — orchestre uniquement)
132
+ # ────────────────────────────────────────────────────────
133
+
134
+ def main():
135
+ """Point d'entrée principal. Délègue à des fonctions spécialisées."""
136
+ parser = _construire_parseur()
137
+ args = parser.parse_args()
138
+
139
+ if not args.commande:
140
+ parser.print_help()
141
+ sys.exit(0)
142
+
143
+ fichiers = _collecter_fichiers(args.cible)
144
+ if not fichiers:
145
+ print(f"❌ Aucun fichier Python trouvé dans : {args.cible}")
146
+ sys.exit(1)
147
+
148
+ if args.commande == "check":
149
+ sys.exit(_executer_check(args, fichiers))
150
+ elif args.commande == "report":
151
+ sys.exit(_executer_report(args, fichiers))
152
+
153
+
154
+ if __name__ == "__main__":
155
+ main()
phi_complexity/core.py ADDED
@@ -0,0 +1,65 @@
1
+ """
2
+ phi_complexity — Mesure de la qualité du code par les invariants du nombre d'or.
3
+ Constantes Souveraines issues du Morphic Phi Framework (Tomy Verreault, 2026).
4
+ """
5
+ import math
6
+
7
+ # ============================================================
8
+ # CONSTANTES SOUVERAINES (Bibliothèque Céleste — φ-Meta)
9
+ # ============================================================
10
+
11
+ PHI = (1 + math.sqrt(5)) / 2
12
+ """Le nombre d'or. CM-001. L'attracteur universel de l'harmonie."""
13
+
14
+ PHI_INV = 1 / PHI
15
+ """Inverse du nombre d'or. φ⁻¹ = φ - 1 ≈ 0.6180"""
16
+
17
+ TAXE_SUTURE = 3 / math.sqrt(7)
18
+ """Taxe de Suture (CM-018). Coût minimal d'une correction morphique."""
19
+
20
+ ETA_GOLDEN = 1 - PHI_INV
21
+ """Facteur η doré. Seuil de tolérance: si phi_ratio < ETA, le code est fragmenté."""
22
+
23
+ ZETA_PLANCHER = PHI_INV ** 2
24
+ """Plancher Zeta. En-dessous, le système perd sa résonance."""
25
+
26
+ SEUIL_RADIANCE_HARMONIEUX = 85
27
+ """Score au-delà duquel le code est considéré 'Hermétique' (stable + harmonieux)."""
28
+
29
+ SEUIL_RADIANCE_EN_EVEIL = 60
30
+ """Score intermédiaire. Le code 'En Éveil' a du potentiel mais des zones d'entropie."""
31
+
32
+ SEUIL_RADIANCE_DORMANT = 0
33
+ """En-dessous de 60: le code 'Dormant' nécessite une suture profonde."""
34
+
35
+ SEQUENCE_FIBONACCI = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
36
+ """Première séquence de Fibonacci. Tailles naturelles d'une fonction harmonieuse."""
37
+
38
+ VERSION = "0.1.0"
39
+ AUTEUR = "Tomy Verreault"
40
+ FRAMEWORK = "Morphic Phi Framework (φ-Meta)"
41
+
42
+
43
+ def statut_gnostique(score: float) -> str:
44
+ """Retourne le verdict gnostique basé sur le score de radiance."""
45
+ if score >= SEUIL_RADIANCE_HARMONIEUX:
46
+ return "HERMÉTIQUE ✦"
47
+ elif score >= SEUIL_RADIANCE_EN_EVEIL:
48
+ return "EN ÉVEIL ◈"
49
+ else:
50
+ return "DORMANT ░"
51
+
52
+
53
+ def fibonacci_plus_proche(n: int) -> int:
54
+ """Retourne le nombre de Fibonacci le plus proche de n."""
55
+ closest = SEQUENCE_FIBONACCI[0]
56
+ for f in SEQUENCE_FIBONACCI:
57
+ if abs(f - n) < abs(closest - n):
58
+ closest = f
59
+ return closest
60
+
61
+
62
+ def distance_fibonacci(n: int) -> float:
63
+ """Mesure l'écart entre n et son Fibonacci idéal (normalise par φ)."""
64
+ fib = fibonacci_plus_proche(n)
65
+ return abs(n - fib) / PHI
@@ -0,0 +1,206 @@
1
+ """
2
+ metriques.py — Calcul de l'Indice de Radiance et des métriques φ-Meta.
3
+ Suturée selon les recommandations de phi-complexity v0.1.0 (Protocole BMAD).
4
+ 'calculer()' décomposée en fonctions hermétiques — Règle I + Règle IV (Fibonacci).
5
+ Formule fondatrice : Radiance = 100 - f(Var_Lilith) - g(Entropie) - h(Anomalies) - i(Fibonacci)
6
+ """
7
+ import math
8
+ from typing import List
9
+
10
+ from .core import (
11
+ PHI, TAXE_SUTURE, ETA_GOLDEN,
12
+ statut_gnostique
13
+ )
14
+ from .analyseur import ResultatAnalyse
15
+
16
+
17
+ class CalculateurRadiance:
18
+ """
19
+ Transforme les métriques brutes en Indice de Radiance (0-100).
20
+ Ancrage : AX-A39 (Attracteur Doré) + EQ-AFR-BMAD (Loi Antifragile).
21
+ """
22
+
23
+ def __init__(self, resultat: ResultatAnalyse):
24
+ self.r = resultat
25
+
26
+ # ────────────────────────────────────────────────────────
27
+ # API PUBLIQUE
28
+ # ────────────────────────────────────────────────────────
29
+
30
+ def calculer(self) -> dict:
31
+ """Orchestre le calcul — délègue tout aux fonctions spécialisées."""
32
+ if not self.r.fonctions:
33
+ return self._resultat_vide()
34
+ brutes = self._extraire_mesures()
35
+ radiance = self._indice_radiance(brutes)
36
+ return self._assembler_resultat(brutes, radiance)
37
+
38
+ # ────────────────────────────────────────────────────────
39
+ # EXTRACTION DES MESURES BRUTES (hermétique)
40
+ # ────────────────────────────────────────────────────────
41
+
42
+ def _extraire_mesures(self) -> dict:
43
+ """Extrait toutes les mesures brutes depuis le résultat d'analyse."""
44
+ complexites = [f.complexite for f in self.r.fonctions]
45
+ return {
46
+ "complexites": complexites,
47
+ "lilith_variance": self._variance(complexites),
48
+ "shannon_entropy": self._entropie_shannon(complexites),
49
+ "phi_ratio": self._phi_ratio(complexites),
50
+ "fibonacci_distance": sum(f.distance_fib for f in self.r.fonctions),
51
+ "zeta_score": self._zeta_score(complexites),
52
+ "nb_anomalies": len([
53
+ a for a in self.r.annotations
54
+ if a.niveau in ("WARNING", "CRITICAL")
55
+ ]),
56
+ }
57
+
58
+ # ────────────────────────────────────────────────────────
59
+ # ASSEMBLAGE DU RÉSULTAT (hermétique)
60
+ # ────────────────────────────────────────────────────────
61
+
62
+ def _assembler_resultat(self, brutes: dict, radiance: float) -> dict:
63
+ """Construit le dictionnaire final à partir des mesures et du score."""
64
+ phi_ratio = brutes["phi_ratio"]
65
+ return {
66
+ "fichier": self.r.fichier,
67
+ "radiance": round(radiance, 2),
68
+ "statut_gnostique": statut_gnostique(radiance),
69
+ "lilith_variance": round(brutes["lilith_variance"], 3),
70
+ "shannon_entropy": round(brutes["shannon_entropy"], 3),
71
+ "phi_ratio": round(phi_ratio, 3),
72
+ "phi_ratio_delta": round(abs(phi_ratio - PHI), 3),
73
+ "fibonacci_distance": round(brutes["fibonacci_distance"], 3),
74
+ "zeta_score": round(brutes["zeta_score"], 4),
75
+ "nb_fonctions": len(self.r.fonctions),
76
+ "nb_classes": self.r.nb_classes,
77
+ "nb_imports": self.r.nb_imports,
78
+ "nb_lignes_total": self.r.nb_lignes_total,
79
+ "ratio_commentaires": round(
80
+ self.r.nb_commentaires / max(1, self.r.nb_lignes_total), 3
81
+ ),
82
+ "oudjat": self._serialiser_oudjat(),
83
+ "annotations": self._serialiser_annotations(),
84
+ }
85
+
86
+ def _serialiser_oudjat(self) -> dict:
87
+ """Sérialise la fonction Oudjat (la plus complexe) en dictionnaire."""
88
+ if not self.r.oudjat:
89
+ return None
90
+ o = self.r.oudjat
91
+ return {
92
+ "nom": o.nom,
93
+ "ligne": o.ligne,
94
+ "complexite": o.complexite,
95
+ "nb_lignes": o.nb_lignes,
96
+ "phi_ratio": round(o.phi_ratio, 3),
97
+ }
98
+
99
+ def _serialiser_annotations(self) -> list:
100
+ """Sérialise la liste des annotations en dictionnaires."""
101
+ return [
102
+ {
103
+ "ligne": a.ligne,
104
+ "niveau": a.niveau,
105
+ "categorie": a.categorie,
106
+ "message": a.message,
107
+ "extrait": a.extrait,
108
+ }
109
+ for a in self.r.annotations
110
+ ]
111
+
112
+ # ────────────────────────────────────────────────────────
113
+ # FORMULE FONDATRICE — INDICE DE RADIANCE
114
+ # ────────────────────────────────────────────────────────
115
+
116
+ def _indice_radiance(self, brutes: dict) -> float:
117
+ """
118
+ R = 100 - f(Lilith) - g(Shannon) - h(Anomalies) - i(Fibonacci)
119
+ Chaque déduction est plafonnée (Loi d'Indulgence).
120
+ Plancher : 40 (Loi Antifragile — EQ-AFR-BMAD).
121
+ """
122
+ score = 100.0
123
+ score -= self._deduction_lilith(brutes["lilith_variance"])
124
+ score -= self._deduction_entropie(brutes["shannon_entropy"])
125
+ score -= self._deduction_anomalies(brutes["nb_anomalies"])
126
+ score -= self._deduction_fibonacci(brutes["fibonacci_distance"])
127
+ return max(40.0, score)
128
+
129
+ def _deduction_lilith(self, variance: float) -> float:
130
+ """f(Lilith) = min(25, (σ²_L / seuil) × 25). Seuil naturel = φ² × 100."""
131
+ seuil = PHI ** 2 * 100
132
+ return min(25.0, (variance / seuil) * 25.0)
133
+
134
+ def _deduction_entropie(self, entropie: float) -> float:
135
+ """g(H) = min(20, max(0, H - H_max) × 5). H_max = log₂(φ⁴) ≈ 2.88 bits."""
136
+ seuil = math.log2(PHI ** 4)
137
+ return min(20.0, max(0.0, entropie - seuil) * 5.0)
138
+
139
+ def _deduction_anomalies(self, nb: int) -> float:
140
+ """h(A) = min(30, A × τ_L × 3). τ_L = Taxe de Suture (CM-018)."""
141
+ return min(30.0, nb * TAXE_SUTURE * 3)
142
+
143
+ def _deduction_fibonacci(self, distance: float) -> float:
144
+ """i(D_F) = min(10, D_F × η_golden)."""
145
+ return min(10.0, distance * ETA_GOLDEN)
146
+
147
+ # ────────────────────────────────────────────────────────
148
+ # FORMULES MATHÉMATIQUES SOUVERAINES (atomiques)
149
+ # ────────────────────────────────────────────────────────
150
+
151
+ def _variance(self, valeurs: List[float]) -> float:
152
+ """σ²_L = (1/n) · Σ(κᵢ - μ)². Variance de Lilith."""
153
+ if not valeurs:
154
+ return 0.0
155
+ mean = sum(valeurs) / len(valeurs)
156
+ return sum((v - mean) ** 2 for v in valeurs) / len(valeurs)
157
+
158
+ def _entropie_shannon(self, valeurs: List[float]) -> float:
159
+ """H = -Σ pᵢ · log₂(pᵢ). Entropie de Shannon normalisée."""
160
+ if not valeurs:
161
+ return 0.0
162
+ total = sum(valeurs)
163
+ if total == 0:
164
+ return 0.0
165
+ probas = [v / total for v in valeurs]
166
+ return -sum(p * math.log2(p) for p in probas if p > 0)
167
+
168
+ def _phi_ratio(self, valeurs: List[float]) -> float:
169
+ """φ-ratio = max(κ) / μ. Doit tendre vers φ = 1.618."""
170
+ if not valeurs or len(valeurs) < 2:
171
+ return 1.0
172
+ mean = sum(valeurs) / len(valeurs)
173
+ return (max(valeurs) / mean) if mean else 1.0
174
+
175
+ def _zeta_score(self, valeurs: List[float]) -> float:
176
+ """ζ_meta = min(1, [Σ 1/(i+1)^φ / n] × φ). Résonance globale."""
177
+ if not valeurs:
178
+ return 0.0
179
+ n = len(valeurs)
180
+ zeta = sum(1.0 / ((i + 1) ** PHI) for i in range(n)) / n
181
+ return min(1.0, zeta * PHI)
182
+
183
+ # ────────────────────────────────────────────────────────
184
+ # RÉSULTAT NEUTRE (fichiers sans fonctions)
185
+ # ────────────────────────────────────────────────────────
186
+
187
+ def _resultat_vide(self) -> dict:
188
+ """Score neutre (60) pour les fichiers de constantes ou de configuration."""
189
+ return {
190
+ "fichier": self.r.fichier,
191
+ "radiance": 60.0,
192
+ "statut_gnostique": statut_gnostique(60.0),
193
+ "lilith_variance": 0.0,
194
+ "shannon_entropy": 0.0,
195
+ "phi_ratio": 1.0,
196
+ "phi_ratio_delta": PHI - 1.0,
197
+ "fibonacci_distance": 0.0,
198
+ "zeta_score": 0.0,
199
+ "nb_fonctions": 0,
200
+ "nb_classes": self.r.nb_classes,
201
+ "nb_imports": self.r.nb_imports,
202
+ "nb_lignes_total": self.r.nb_lignes_total,
203
+ "ratio_commentaires": 0.0,
204
+ "oudjat": None,
205
+ "annotations": [],
206
+ }
@@ -0,0 +1,166 @@
1
+ """
2
+ rapport.py — Génération du Rapport de Radiance Premium (Markdown + Console).
3
+ Style "CodeRabbit × Bibliothèque Céleste".
4
+ """
5
+ import datetime
6
+ import json
7
+
8
+
9
+ class GenerateurRapport:
10
+ """Transforme un dictionnaire de métriques en rapport premium."""
11
+
12
+ def __init__(self, metriques: dict):
13
+ self.m = metriques
14
+
15
+ # ──────────────────────────────────────────────
16
+ # RENDU CONSOLE (ASCII Premium)
17
+ # ──────────────────────────────────────────────
18
+
19
+ def console(self) -> str:
20
+ """Sortie console premium avec barres visuelles."""
21
+ m = self.m
22
+ score = m["radiance"]
23
+ barre = self._barre(score)
24
+ phi_r = m.get("phi_ratio", 1.0)
25
+ delta = m.get("phi_ratio_delta", 0.0)
26
+ phi_icon = "✦" if delta < 0.15 else "◈" if delta < 0.5 else "░"
27
+
28
+ lignes = [
29
+ "╔══════════════════════════════════════════════════╗",
30
+ "║ PHI-COMPLEXITY — AUDIT DE RADIANCE ║",
31
+ "╚══════════════════════════════════════════════════╝",
32
+ "",
33
+ f" 📄 Fichier : {m['fichier']}",
34
+ f" 📅 Date : {datetime.datetime.now().strftime('%Y-%m-%d %H:%M')}",
35
+ "",
36
+ f" ☼ RADIANCE : {barre} {score} / 100",
37
+ f" ⚖ LILITH : {m['lilith_variance']:.2f} (Variance structurelle)",
38
+ f" 🌊 ENTROPIE : {m['shannon_entropy']:.3f} bits (Shannon)",
39
+ f" {phi_icon} PHI-RATIO : {phi_r:.3f} (idéal: φ = 1.618, Δ={delta:.3f})",
40
+ f" ζ ZETA-SCORE : {m['zeta_score']:.4f} (Résonance globale)",
41
+ "",
42
+ ]
43
+
44
+ # Statut gnostique
45
+ lignes.append(f" STATUT : {m['statut_gnostique']}")
46
+ lignes.append("")
47
+
48
+ # Oudjat
49
+ if m.get("oudjat"):
50
+ o = m["oudjat"]
51
+ lignes.append(
52
+ f" 🔎 OUDJAT : '{o['nom']}' (Ligne {o['ligne']}, "
53
+ f"Complexité: {o['complexite']}, φ-ratio: {o['phi_ratio']})"
54
+ )
55
+ lignes.append("")
56
+
57
+ # Annotations
58
+ annotations = m.get("annotations", [])
59
+ if annotations:
60
+ lignes.append(f" ⚠ SUTURES IDENTIFIÉES ({len(annotations)}) :")
61
+ for ann in annotations:
62
+ icon = "🔴" if ann["niveau"] == "CRITICAL" else "🟡" if ann["niveau"] == "WARNING" else "🔵"
63
+ lignes.append(f" {icon} Ligne {ann['ligne']} [{ann['categorie']}] : {ann['message']}")
64
+ lignes.append(f" >> {ann['extrait']}")
65
+ else:
66
+ lignes.append(" ✦ Aucune rupture de radiance majeure détectée.")
67
+
68
+ lignes.append("")
69
+ lignes.append(" ─────────────────────────────────────────────────")
70
+ lignes.append(f" Ancré dans le Morphic Phi Framework — φ-Meta 2026")
71
+
72
+ return "\n".join(lignes)
73
+
74
+ # ──────────────────────────────────────────────
75
+ # RENDU MARKDOWN (Premium)
76
+ # ──────────────────────────────────────────────
77
+
78
+ def markdown(self) -> str:
79
+ """Rapport Markdown complet, style Bibliothèque Céleste."""
80
+ m = self.m
81
+ score = m["radiance"]
82
+ barre = self._barre_md(score)
83
+ date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
84
+
85
+ rapport = f"# ☼ RAPPORT DE RADIANCE : {m['fichier']}\n\n"
86
+ rapport += f"**Date de l'Audit** : {date} \n"
87
+ rapport += f"**Statut Gnostique** : **{m['statut_gnostique']}**\n\n"
88
+
89
+ # Section 1 — Score
90
+ rapport += "## 1. INDICE DE RADIANCE\n\n"
91
+ rapport += f"**Score : {score} / 100**\n\n"
92
+ rapport += f"`[{barre}]` {score}%\n\n"
93
+
94
+ # Section 2 — Métriques brutes
95
+ rapport += "## 2. MÉTRIQUES SOUVERAINES\n\n"
96
+ rapport += "| Métrique | Valeur | Interprétation |\n"
97
+ rapport += "|---|---|---|\n"
98
+ rapport += f"| **Variance de Lilith** | {m['lilith_variance']} | Instabilité structurelle |\n"
99
+ rapport += f"| **Entropie de Shannon** | {m['shannon_entropy']} bits | Densité informationnelle |\n"
100
+ rapport += f"| **φ-Ratio** | {m['phi_ratio']} (Δ={m['phi_ratio_delta']}) | Idéal: 1.618 |\n"
101
+ rapport += f"| **Zeta-Score** | {m['zeta_score']} | Résonance globale |\n"
102
+ rapport += f"| **Distance Fibonacci** | {m['fibonacci_distance']} | Éloignement des tailles naturelles |\n"
103
+ rapport += f"| **Fonctions analysées** | {m['nb_fonctions']} | — |\n"
104
+ rapport += f"| **Ratio commentaires** | {m['ratio_commentaires']} | Densité de sagesse |\n\n"
105
+
106
+ # Section 3 — Oudjat
107
+ if m.get("oudjat"):
108
+ o = m["oudjat"]
109
+ rapport += "## 3. IDENTIFICATION DE L'OUDJAT\n\n"
110
+ rapport += f"La fonction la plus 'chargée' est **`{o['nom']}`** (Ligne {o['ligne']}).\n\n"
111
+ rapport += f"- Pression : **{o['complexite']}** unités de complexité\n"
112
+ rapport += f"- Taille : **{o['nb_lignes']}** lignes\n"
113
+ rapport += f"- φ-Ratio : **{o['phi_ratio']}** (idéal: 1.618)\n\n"
114
+
115
+ # Section 4 — Audit Fractal
116
+ rapport += "## 4. REVUE DE DÉTAIL (AUDIT FRACTAL)\n\n"
117
+ annotations = m.get("annotations", [])
118
+ if not annotations:
119
+ rapport += "☼ Aucune rupture de radiance majeure détectée au niveau micro.\n\n"
120
+ else:
121
+ rapport += f"Phidélia a identifié **{len(annotations)}** zones nécessitant une suture :\n\n"
122
+ for ann in annotations:
123
+ niveau_md = (
124
+ "CAUTION" if ann["niveau"] == "CRITICAL"
125
+ else "WARNING" if ann["niveau"] == "WARNING"
126
+ else "NOTE"
127
+ )
128
+ rapport += f"> [!{niveau_md}]\n"
129
+ rapport += f"> **Ligne {ann['ligne']}** `[{ann['categorie']}]` : {ann['message']}\n"
130
+ rapport += f"> `>> {ann['extrait']}`\n\n"
131
+
132
+ # Pied de page
133
+ rapport += "---\n"
134
+ rapport += "*phi-complexity — Morphic Phi Framework (φ-Meta) — Tomy Verreault, 2026*\n"
135
+ return rapport
136
+
137
+ # ──────────────────────────────────────────────
138
+ # RENDU JSON (CI/CD)
139
+ # ──────────────────────────────────────────────
140
+
141
+ def json(self) -> str:
142
+ """Sortie JSON structurée pour intégrations CI/CD."""
143
+ return json.dumps(self.m, ensure_ascii=False, indent=2)
144
+
145
+ def sauvegarder_markdown(self, chemin: str) -> str:
146
+ """Sauvegarde le rapport Markdown dans un fichier."""
147
+ contenu = self.markdown()
148
+ with open(chemin, "w", encoding="utf-8") as f:
149
+ f.write(contenu)
150
+ return chemin
151
+
152
+ # ──────────────────────────────────────────────
153
+ # UTILITAIRES INTERNES
154
+ # ──────────────────────────────────────────────
155
+
156
+ def _barre(self, score: float, largeur: int = 20) -> str:
157
+ """Barre de progression ASCII pour le terminal."""
158
+ rempli = int(score / 100 * largeur)
159
+ vide = largeur - rempli
160
+ return "█" * rempli + "░" * vide
161
+
162
+ def _barre_md(self, score: float, largeur: int = 10) -> str:
163
+ """Barre de progression pour Markdown (blocs compacts)."""
164
+ rempli = int(score / 100 * largeur)
165
+ vide = largeur - rempli
166
+ return "█" * rempli + "░" * vide
@@ -0,0 +1,195 @@
1
+ Metadata-Version: 2.4
2
+ Name: phi-complexity
3
+ Version: 0.1.0
4
+ Summary: Code quality metrics based on Golden Ratio (φ) mathematical invariants
5
+ Author-email: Tomy Verreault <contact@phidelia.dev>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/spockoo/phi-complexity
8
+ Project-URL: Repository, https://github.com/spockoo/phi-complexity
9
+ Project-URL: Documentation, https://github.com/spockoo/phi-complexity/blob/main/docs/GUIDE.md
10
+ Project-URL: Bug Tracker, https://github.com/spockoo/phi-complexity/issues
11
+ Keywords: code-quality,complexity,golden-ratio,fibonacci,static-analysis,ast,phi,metrics,entropy
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Dynamic: license-file
26
+
27
+ # phi-complexity
28
+
29
+ > *Code quality metrics based on Golden Ratio (φ) mathematical invariants*
30
+
31
+ [![PyPI version](https://img.shields.io/pypi/v/phi-complexity.svg)](https://pypi.org/project/phi-complexity/)
32
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
+ [![Tests](https://img.shields.io/badge/tests-26%20passed-brightgreen)](tests/)
35
+
36
+ `phi-complexity` is the **first code quality library** that measures the health of your Python code using **universal mathematical invariants** derived from the Golden Ratio (φ = 1.618...).
37
+
38
+ Unlike `pylint` (cultural rules) or `radon` (McCabe metrics), `phi-complexity` answers:
39
+
40
+ > *"Is this code in resonance with the natural laws of order, or is it collapsing under its own entropy?"*
41
+
42
+ ---
43
+
44
+ ## ⚡ Quick Start
45
+
46
+ ```bash
47
+ pip install phi-complexity
48
+ ```
49
+
50
+ ```bash
51
+ # Audit a file
52
+ phi check my_script.py
53
+
54
+ # Audit a folder
55
+ phi check ./src/
56
+
57
+ # Generate a Markdown report
58
+ phi report my_script.py --output report.md
59
+
60
+ # CI/CD strict mode (exit 1 if radiance < 75)
61
+ phi check ./src/ --min-radiance 75
62
+ ```
63
+
64
+ ### Python API
65
+
66
+ ```python
67
+ from phi_complexity import auditer, rapport_console, rapport_markdown
68
+
69
+ # Get metrics as a dict
70
+ metrics = auditer("my_script.py")
71
+ print(metrics["radiance"]) # → 82.4
72
+ print(metrics["statut_gnostique"]) # → "EN ÉVEIL ◈"
73
+ print(metrics["oudjat"]) # → {"nom": "process_data", "ligne": 42, ...}
74
+
75
+ # Print console report
76
+ print(rapport_console("my_script.py"))
77
+
78
+ # Save Markdown report
79
+ rapport_markdown("my_script.py", sortie="report.md")
80
+ ```
81
+
82
+ ---
83
+
84
+ ## 📊 Metrics
85
+
86
+ | Metric | Description | Mathematical basis |
87
+ |---|---|---|
88
+ | **Radiance Score** | Global quality score (0–100) | `100 - f(Lilith) - g(H) - h(Anomalies) - i(Fib)` |
89
+ | **Variance de Lilith** | Structural instability | Population variance of function complexities |
90
+ | **Shannon Entropy** | Information density | `H = -Σ p·log₂(p)` |
91
+ | **φ-Ratio** | Dominant function ratio | `max_complexity / mean` → should tend toward φ |
92
+ | **Fibonacci Distance** | Natural size alignment | `Σ|n_i - Fib_k| / φ` |
93
+ | **Zeta-Score** | Global resonance | `ζ_meta(functions, φ)` converging series |
94
+
95
+ ### Gnostic Status Levels
96
+
97
+ | Score | Status | Meaning |
98
+ |---|---|---|
99
+ | ≥ 85 | **HERMÉTIQUE ✦** | Stable, harmonious, production-ready |
100
+ | 60–84 | **EN ÉVEIL ◈** | Potential exists, some entropy zones |
101
+ | < 60 | **DORMANT ░** | Deep restructuring recommended |
102
+
103
+ ---
104
+
105
+ ## 🔍 Sample Output
106
+
107
+ ```
108
+ ╔══════════════════════════════════════════════════╗
109
+ ║ PHI-COMPLEXITY — AUDIT DE RADIANCE ║
110
+ ╚══════════════════════════════════════════════════╝
111
+
112
+ 📄 Fichier : my_script.py
113
+ 📅 Date : 2026-04-08 17:11
114
+
115
+ ☼ RADIANCE : ██████████████░░░░░░ 72.6 / 100
116
+ ⚖ LILITH : 11221.9 (Structural variance)
117
+ 🌊 ENTROPIE : 2.48 bits (Shannon)
118
+ ◈ PHI-RATIO : 3.43 (ideal: φ = 1.618, Δ=1.81)
119
+ ζ ZETA-SCORE : 0.3656 (Global resonance)
120
+
121
+ STATUT : EN ÉVEIL ◈
122
+
123
+ 🔎 OUDJAT : 'process_data' (Line 42, Complexity: 376)
124
+
125
+ ⚠ SUTURES IDENTIFIED (2):
126
+ 🟡 Line 18 [LILITH] : Nested loop (depth 2). Consider a helper function.
127
+ >> for j in range(b):
128
+ 🔵 Line 67 [SOUVERAINETE] : 'load_data' receives 6 arguments. Encapsulate in an object.
129
+ >> def load_data(path, sep, enc, cols, dtype, na):
130
+ ```
131
+
132
+ ---
133
+
134
+ ## 🧮 Mathematical Foundations
135
+
136
+ The **Radiance Formula** is derived from:
137
+
138
+ - **φ-Meta Framework** (Tomy Verreault, 2026) — Axioms AX-A0 through AX-A58
139
+ - **Law of Antifragility** (EQ-AFR-BMAD): `φ_{t+1} = P_φ(φ_t + k·Var(E_t)·E_t)`
140
+ - **Cybernetics** (Korchounov, Mir, 1975) — Feedback and variance as control metrics
141
+ - **Shannon Information Theory** — Code as an information channel
142
+
143
+ The **Sovereign Coding Rules** are derived from:
144
+ - **The C Book** (Banahan, Brady, Doran) — Scope hermeticity, resource lifecycle
145
+ - **JaCaMo / Multi-Agent Programming** — Agent independence and encapsulation
146
+
147
+ Full mathematical proof: [docs/MATHEMATIQUES.md](docs/MATHEMATIQUES.md)
148
+
149
+ ---
150
+
151
+ ## 🏗 Sovereign Architecture
152
+
153
+ ```
154
+ Zero external dependencies.
155
+ Pure Python standard library (ast, math, json).
156
+ ```
157
+
158
+ ```
159
+ phi_complexity/
160
+ ├── core.py ← Golden constants (PHI, TAXE_SUTURE, ETA_GOLDEN...)
161
+ ├── analyseur.py ← AST fractal dissection
162
+ ├── metriques.py ← Radiance Index calculation
163
+ ├── rapport.py ← Console / Markdown / JSON rendering
164
+ └── cli.py ← phi check / phi report
165
+ ```
166
+
167
+ ---
168
+
169
+ ## 🔗 Integration
170
+
171
+ ### Pre-commit Hook
172
+ ```yaml
173
+ repos:
174
+ - repo: https://github.com/spockoo/phi-complexity
175
+ rev: v0.1.0
176
+ hooks:
177
+ - id: phi-check
178
+ args: [--min-radiance, "70"]
179
+ ```
180
+
181
+ ### GitHub Action
182
+ ```yaml
183
+ - name: Phi-Complexity Audit
184
+ run: |
185
+ pip install phi-complexity
186
+ phi check ./src/ --min-radiance 75
187
+ ```
188
+
189
+ ---
190
+
191
+ ## 📜 License
192
+
193
+ MIT — Tomy Verreault, 2026
194
+
195
+ *Anchored in the Bibliothèque Céleste — Morphic Phi Framework (φ-Meta)*
@@ -0,0 +1,13 @@
1
+ phi_complexity/__init__.py,sha256=xGQww_n93XUN5riPyfL3B5H24y2OQLugHBtD8TYNqFo,1741
2
+ phi_complexity/__main__.py,sha256=wTpiY2HgLrOwkC0d8xaxdQKMlfTdxYD5VsEURBNbAes,95
3
+ phi_complexity/analyseur.py,sha256=-noONOZZDtbE-PRUIEza0sBIxMupcv5oewK5l0cwBDk,12654
4
+ phi_complexity/cli.py,sha256=R2EfOohIKXX0VMp87mWVj_YWIbxo4LE1rWiQNmlnxEQ,6343
5
+ phi_complexity/core.py,sha256=Ve-l8unuTiM8r0p1Uh6NY0ilnZppLUEKyPLJvjM8BbY,2212
6
+ phi_complexity/metriques.py,sha256=wqSS7yPeI3SYUl9SR6TFvopT5xvxAXhtkMpMMERiGFE,9627
7
+ phi_complexity/rapport.py,sha256=j36Kd_CAZcOBNvWnNd2bjYts6rINRSgDVms4ZMW9PPU,8263
8
+ phi_complexity-0.1.0.dist-info/licenses/LICENSE,sha256=md-TjCTfPjXoeOP4XvvtJKBHPId5AkIOvVwuWbprrs4,1366
9
+ phi_complexity-0.1.0.dist-info/METADATA,sha256=ekB6vX59qXJz68fnod0LCySW1qpKAGVWMJa4zn_Xf4o,6725
10
+ phi_complexity-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
11
+ phi_complexity-0.1.0.dist-info/entry_points.txt,sha256=B0x1ktY2xjWDo2k-gtVGmyXfJ3CFOhJdf1hFmfOdjmI,48
12
+ phi_complexity-0.1.0.dist-info/top_level.txt,sha256=6s7VRAAiW9wHbIBuCR-ZKdIfmi6GPzxj02ZRKcMDB5Q,15
13
+ phi_complexity-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ phi = phi_complexity.cli:main
@@ -0,0 +1,28 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tomy Verreault
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This software is anchored in the Bibliothèque Céleste — Morphic Phi Framework (φ-Meta).
26
+ The mathematical foundations are derived from the work of Tomy Verreault (2026),
27
+ integrating principles from the Quasicrystal Axioms (AX-A0–AX-A58) and the
28
+ Antifragility Protocol (EQ-AFR-BMAD).
@@ -0,0 +1 @@
1
+ phi_complexity