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.
- phi_complexity/__init__.py +65 -0
- phi_complexity/__main__.py +3 -0
- phi_complexity/analyseur.py +281 -0
- phi_complexity/cli.py +155 -0
- phi_complexity/core.py +65 -0
- phi_complexity/metriques.py +206 -0
- phi_complexity/rapport.py +166 -0
- phi_complexity-0.1.0.dist-info/METADATA +195 -0
- phi_complexity-0.1.0.dist-info/RECORD +13 -0
- phi_complexity-0.1.0.dist-info/WHEEL +5 -0
- phi_complexity-0.1.0.dist-info/entry_points.txt +2 -0
- phi_complexity-0.1.0.dist-info/licenses/LICENSE +28 -0
- phi_complexity-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -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,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
|
+
[](https://pypi.org/project/phi-complexity/)
|
|
32
|
+
[](https://www.python.org/downloads/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
[](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,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
|