phi-complexity 0.13.1__tar.gz → 0.14.1__tar.gz
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-0.13.1 → phi_complexity-0.14.1}/PKG-INFO +5 -2
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/README.md +4 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/carte.py +20 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/core.py +1 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/croyances.py +11 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/dependances.py +42 -54
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/indexeur.py +4 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/eft.py +1 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/lean.py +185 -4
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/registry.py +16 -10
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/treesitter_generic.py +19 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/mission.py +8 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/modeles.py +3 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/oracle.py +1 -1
- phi_complexity-0.14.1/phi_complexity/parseur_autonome.py +870 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/parseur_lean.py +77 -2
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/veille.py +71 -6
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/PKG-INFO +5 -2
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/SOURCES.txt +6 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/pyproject.toml +1 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_ancrage.py +1 -1
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_durcissement.py +13 -11
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_durcissement_20261001.py +8 -2
- phi_complexity-0.14.1/tests/test_durcissement_extraction_20261002.py +179 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_lean.py +10 -4
- phi_complexity-0.14.1/tests/test_parseur_autonome.py +221 -0
- phi_complexity-0.14.1/tests/test_veille_alternatif.py +187 -0
- phi_complexity-0.14.1/tests/test_zero_treesitter_silencieux.py +187 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/LICENSE +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/__init__.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/__main__.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/analyseur.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/ancrage.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/bayes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/chemins_verifiables.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/cli.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/durcissement.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/__init__.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/panneau_phi.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/recherche.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/tampon.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/editeur/tui.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/entropie.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/explorateur.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/formules/__init__.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/formules/nodes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/formules/parser.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/__init__.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/base.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/langs/python_native.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/metriques.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/piste_sorry.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/rapport.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity/sondes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/dependency_links.txt +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/entry_points.txt +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/requires.txt +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/phi_complexity.egg-info/top_level.txt +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/setup.cfg +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_adiabatique.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_analyseur.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_atteignabilite.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_bayes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_carte.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_chemins_b6_memoire.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_chemins_durcissement.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_chemins_multilemmes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_chemins_verifiables.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_cli.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_core.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_croyances.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_durcissement_sondes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_editeur.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_eft.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_elagage.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_encerclement.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_entropie.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_explorateur.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_formules.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_mission.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_multilang.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_piste_sorry.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_rapport.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_scoreur_type.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_sondes.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_synthese_structures.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_veille.py +0 -0
- {phi_complexity-0.13.1 → phi_complexity-0.14.1}/tests/test_vitesse.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: phi-complexity
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.14.1
|
|
4
4
|
Summary: Multi-language code quality metrics based on Golden Ratio (φ) mathematical invariants and adiabatic equilibrium
|
|
5
5
|
Author-email: Tomy Verreault <contact@phidelia.dev>
|
|
6
6
|
License: MIT
|
|
@@ -50,7 +50,10 @@ Unlike `pylint` (cultural rules) or `radon` (McCabe metrics), `phi-complexity` a
|
|
|
50
50
|
pip install git+https://github.com/spockoo/Phi-Complexity.git
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
> Note : le paquet est aussi sur PyPI (`pip install phi-complexity`, 0.
|
|
53
|
+
> Note : le paquet est aussi sur PyPI (`pip install phi-complexity`, 0.14.1).
|
|
54
|
+
> Nouveauté 0.14.1 : parseur Lean 4 autonome (stdlib uniquement, zéro dépendance
|
|
55
|
+
> externe) — l'analyse Lean ne nécessite plus tree-sitter ; durcissement de
|
|
56
|
+
> l'extraction (la veille ne devient plus aveugle en silence).
|
|
54
57
|
|
|
55
58
|
```bash
|
|
56
59
|
# Audit a file
|
|
@@ -21,7 +21,10 @@ Unlike `pylint` (cultural rules) or `radon` (McCabe metrics), `phi-complexity` a
|
|
|
21
21
|
pip install git+https://github.com/spockoo/Phi-Complexity.git
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
> Note : le paquet est aussi sur PyPI (`pip install phi-complexity`, 0.
|
|
24
|
+
> Note : le paquet est aussi sur PyPI (`pip install phi-complexity`, 0.14.1).
|
|
25
|
+
> Nouveauté 0.14.1 : parseur Lean 4 autonome (stdlib uniquement, zéro dépendance
|
|
26
|
+
> externe) — l'analyse Lean ne nécessite plus tree-sitter ; durcissement de
|
|
27
|
+
> l'extraction (la veille ne devient plus aveugle en silence).
|
|
25
28
|
|
|
26
29
|
```bash
|
|
27
30
|
# Audit a file
|
|
@@ -142,6 +142,26 @@ def carte_projet(dossier: str, lang: Optional[str] = None,
|
|
|
142
142
|
non_supportes = fichiers_non_supportes(dossier, exclusions=exclusions,
|
|
143
143
|
langage=lang)
|
|
144
144
|
avertissements = _avertissements_backend()
|
|
145
|
+
# Verrouillage « zéro tree-sitter silencieux » (2026-10-03) : si des
|
|
146
|
+
# symboles n'existent que grâce au repli robuste (tree-sitter les a
|
|
147
|
+
# avalés), on le dit explicitement dans les avertissements — la clé
|
|
148
|
+
# existe déjà, aucun changement de schéma.
|
|
149
|
+
par_fichier_repli = {}
|
|
150
|
+
for chemin, symboles in index.items():
|
|
151
|
+
n = sum(1 for s in symboles
|
|
152
|
+
if getattr(s, "extraction", "analyseur") == "robuste_repli")
|
|
153
|
+
if n:
|
|
154
|
+
par_fichier_repli[os.path.basename(chemin)] = n
|
|
155
|
+
if par_fichier_repli:
|
|
156
|
+
total_repli = sum(par_fichier_repli.values())
|
|
157
|
+
avertissements.append(
|
|
158
|
+
f"⚠️ EXTRACTION DÉGRADÉE — repli robuste : {total_repli} "
|
|
159
|
+
f"symbole(s) récupéré(s) dans {len(par_fichier_repli)} "
|
|
160
|
+
f"fichier(s) (invisibles à tree-sitter, métriques = proxy "
|
|
161
|
+
f"lignes) : " + ", ".join(
|
|
162
|
+
f"{f} ({n})" for f, n in sorted(par_fichier_repli.items())))
|
|
163
|
+
# (docstring : en mode complet, les clés sont EXACTEMENT celles de
|
|
164
|
+
# v0.7.0 — on n'ajoute AUCUNE clé, on remplit `avertissements`.)
|
|
145
165
|
|
|
146
166
|
fichiers = []
|
|
147
167
|
radiances = []
|
|
@@ -48,7 +48,7 @@ ENTROPIE_CIBLE_NORMALISEE = PHI_INV
|
|
|
48
48
|
BANDE_TOLERANCE_ADIABATIQUE = ETA_GOLDEN
|
|
49
49
|
"""Bande passante de tolérance pour l'entropie relative : η_golden = 1 - φ⁻¹ ≈ 0.381966."""
|
|
50
50
|
|
|
51
|
-
VERSION = "0.
|
|
51
|
+
VERSION = "0.14.1"
|
|
52
52
|
AUTEUR = "Tomy Verreault"
|
|
53
53
|
FRAMEWORK = "Morphic Phi Framework (φ-Meta)"
|
|
54
54
|
|
|
@@ -577,10 +577,17 @@ def chemins_croyants(dossier: str, lang: Optional[str] = None,
|
|
|
577
577
|
sorry_modes = set()
|
|
578
578
|
|
|
579
579
|
croyances: List[CroyanceSymbole] = []
|
|
580
|
+
# Durcissement 2026-10-02 : par fichier, combien de symboles viennent
|
|
581
|
+
# du repli robuste (tree-sitter aveugle). Jamais silencieux.
|
|
582
|
+
extraction_repli: Dict[str, int] = {}
|
|
580
583
|
for f in fichiers:
|
|
581
584
|
c = canon[f]
|
|
582
585
|
rel = os.path.relpath(c, dossier_abs)
|
|
583
586
|
symboles = sorted(index[f], key=lambda s: s.ligne)
|
|
587
|
+
n_repli = sum(1 for s in symboles
|
|
588
|
+
if getattr(s, "extraction", "analyseur") == "robuste_repli")
|
|
589
|
+
if n_repli:
|
|
590
|
+
extraction_repli[rel] = n_repli
|
|
584
591
|
sorry_map, mode = _sorry_par_symbole(f, symboles)
|
|
585
592
|
sorry_modes.add(mode)
|
|
586
593
|
if mode == "grep_repli":
|
|
@@ -676,6 +683,10 @@ def chemins_croyants(dossier: str, lang: Optional[str] = None,
|
|
|
676
683
|
"imports_non_resolus": non_resolus[:20],
|
|
677
684
|
"nb_imports_non_resolus": len(non_resolus),
|
|
678
685
|
"limites": LIMITE_HONNETETE,
|
|
686
|
+
# Durcissement 2026-10-02 : fichiers où le repli robuste a récupéré
|
|
687
|
+
# des symboles invisibles à tree-sitter ({fichier_rel: n}).
|
|
688
|
+
# Absent/vide = extraction nominale partout.
|
|
689
|
+
"extraction_repli": extraction_repli,
|
|
679
690
|
}
|
|
680
691
|
if mode_exact:
|
|
681
692
|
# Clé EFT : uniquement en mode exact (JSON flottant inchangé).
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Extraction des dépendances entre définitions Lean.
|
|
2
2
|
|
|
3
|
-
Utilise le parseur
|
|
4
|
-
|
|
3
|
+
Utilise le parseur autonome (parseur_autonome.py, 2026-10-03, autonomie
|
|
4
|
+
stricte) pour :
|
|
5
|
+
1. Extraire toutes les déclarations d’un fichier (spans exacts).
|
|
5
6
|
2. Pour chaque déclaration, trouver les identifiants référencés dans son corps.
|
|
6
7
|
3. Construire le graphe de dépendances.
|
|
7
8
|
|
|
8
|
-
Limites honnêtes :
|
|
9
|
+
Zéro dépendance externe. Limites honnêtes :
|
|
9
10
|
- Syntaxique, pas sémantique : on extrait les identifiants textuels,
|
|
10
11
|
sans résolution de noms complète (pas de distinction entre
|
|
11
12
|
variable liée et référence globale sans analyse de portée).
|
|
@@ -16,10 +17,10 @@ Limites honnêtes :
|
|
|
16
17
|
|
|
17
18
|
from __future__ import annotations
|
|
18
19
|
|
|
20
|
+
import re
|
|
19
21
|
from dataclasses import dataclass, field
|
|
20
22
|
from typing import Dict, List, Set
|
|
21
23
|
|
|
22
|
-
from .parseur_lean import parse, extraire_declarations, extraire_declarations_robuste
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
# Mots-clés Lean à ignorer (non des dépendances)
|
|
@@ -88,24 +89,39 @@ class GrapheDependances:
|
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
def extraire_identifiants(code: str) -> Set[str]:
|
|
91
|
-
"""Extrait tous les identifiants d'un fragment de code Lean.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
92
|
+
"""Extrait tous les identifiants d'un fragment de code Lean.
|
|
93
|
+
|
|
94
|
+
Source unique : le lexer du parseur autonome (2026-10-03). Les
|
|
95
|
+
commentaires et chaînes sont exclus par construction (tokens).
|
|
96
|
+
Ne rend jamais un ensemble vide silencieusement : si le code ne
|
|
97
|
+
contient vraiment aucun identifiant, c'est le cas — sinon le
|
|
98
|
+
lexer les voit tous (aucune grammaire externe).
|
|
99
|
+
"""
|
|
100
|
+
from .parseur_autonome import tokenize, identifiants
|
|
101
|
+
toks, _avs, _nc = tokenize(code)
|
|
102
|
+
return {nom for nom, _ligne in identifiants(toks)
|
|
103
|
+
if nom and nom not in MOTS_CLES and not nom.startswith("_")}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
_RE_IDENT = re.compile(r"[A-Za-z_][A-Za-z0-9_']*")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _identifiants_regex(code: str) -> Set[str]:
|
|
110
|
+
"""Repli regex pour `extraire_identifiants` quand tree-sitter est
|
|
111
|
+
indisponible (jamais de silence : ensemble vide seulement si le
|
|
112
|
+
code n'a vraiment aucun identifiant)."""
|
|
113
|
+
return {m.group(0) for m in _RE_IDENT.finditer(code)
|
|
114
|
+
if m.group(0) not in MOTS_CLES
|
|
115
|
+
and not m.group(0).startswith("_")}
|
|
105
116
|
|
|
106
117
|
|
|
107
118
|
def analyser_fichier(chemin: str) -> GrapheDependances:
|
|
108
|
-
"""Analyse un fichier Lean et construit le graphe de dépendances.
|
|
119
|
+
"""Analyse un fichier Lean et construit le graphe de dépendances.
|
|
120
|
+
|
|
121
|
+
Source unique : parseur autonome — déclarations avec spans de corps
|
|
122
|
+
exacts (pas de découpage par lignes approximatif).
|
|
123
|
+
"""
|
|
124
|
+
from .parseur_autonome import parse_declarations
|
|
109
125
|
graphe = GrapheDependances()
|
|
110
126
|
try:
|
|
111
127
|
with open(chemin, 'r', encoding='utf-8') as f:
|
|
@@ -113,49 +129,21 @@ def analyser_fichier(chemin: str) -> GrapheDependances:
|
|
|
113
129
|
except Exception:
|
|
114
130
|
return graphe
|
|
115
131
|
|
|
116
|
-
|
|
117
|
-
declarations = extraire_declarations_robuste(code)
|
|
118
|
-
noms_declares = {d.nom for d in declarations if d.nom}
|
|
119
|
-
|
|
120
|
-
# Pour chaque déclaration, extraire les identifiants de son corps
|
|
121
|
-
# On utilise une approche simple : découper le code par déclaration
|
|
122
|
-
# (une approche plus précise utiliserait les positions AST)
|
|
123
|
-
for decl in declarations:
|
|
132
|
+
for decl in parse_declarations(code):
|
|
124
133
|
if not decl.nom:
|
|
125
134
|
continue
|
|
126
|
-
|
|
127
|
-
# Simplification : on analyse tout le fichier et on filtre
|
|
128
|
-
# (pour une version précise, il faudrait les offsets AST)
|
|
129
|
-
pass
|
|
130
|
-
|
|
131
|
-
# Approche précise : extraire le corps entre deux déclarations
|
|
132
|
-
# On trie les déclarations par ligne, puis pour chacune,
|
|
133
|
-
# le corps va de sa ligne jusqu'à la ligne de la suivante.
|
|
134
|
-
lignes = code.split('\n')
|
|
135
|
-
decls_triees = sorted(
|
|
136
|
-
[d for d in declarations if d.nom and d.ligne],
|
|
137
|
-
key=lambda d: d.ligne
|
|
138
|
-
)
|
|
139
|
-
for idx, decl in enumerate(decls_triees):
|
|
140
|
-
# Début : ligne de la déclaration (0-indexed)
|
|
141
|
-
debut = decl.ligne - 1
|
|
142
|
-
# Fin : ligne de la prochaine déclaration (exclusive),
|
|
143
|
-
# ou fin du fichier si c'est la dernière
|
|
144
|
-
if idx + 1 < len(decls_triees):
|
|
145
|
-
fin = decls_triees[idx + 1].ligne - 1
|
|
146
|
-
else:
|
|
147
|
-
fin = len(lignes)
|
|
148
|
-
# Extraire le bloc exact
|
|
149
|
-
bloc = '\n'.join(lignes[debut:fin])
|
|
150
|
-
# Extraire les identifiants du bloc
|
|
151
|
-
refs = extraire_identifiants(bloc)
|
|
152
|
-
# Retirer l'auto-référence
|
|
135
|
+
refs = extraire_identifiants(decl.corps)
|
|
153
136
|
refs.discard(decl.nom)
|
|
137
|
+
# Retirer les noms liés par les binders (paramètres, pas dépendances)
|
|
138
|
+
for bnom, _impl in decl.binders:
|
|
139
|
+
refs.discard(bnom)
|
|
154
140
|
graphe.ajouter(decl.nom, refs, chemin, decl.ligne)
|
|
141
|
+
return graphe
|
|
155
142
|
|
|
156
143
|
return graphe
|
|
157
144
|
|
|
158
145
|
|
|
146
|
+
|
|
159
147
|
def dependances_pour(nom: str, graphe: GrapheDependances) -> List[str]:
|
|
160
148
|
"""Retourne la liste triée des dépendances transitives d'un nom,
|
|
161
149
|
en ordre topologique (dépendances d'abord)."""
|
|
@@ -71,6 +71,9 @@ class Symbole:
|
|
|
71
71
|
complexite: int
|
|
72
72
|
langage: str
|
|
73
73
|
fichier: str
|
|
74
|
+
extraction: str = "analyseur" # "analyseur" ou "robuste_repli"
|
|
75
|
+
# (durcissement 2026-10-02 : repli robuste Lean quand tree-sitter
|
|
76
|
+
# a avalé la déclaration — métriques = proxy lignes)
|
|
74
77
|
|
|
75
78
|
|
|
76
79
|
# Cache d'invalidation : (chemin, complet) -> mtime de la dernière
|
|
@@ -103,6 +106,7 @@ def _symboles_du_fichier(chemin: str, langage: Optional[str] = None,
|
|
|
103
106
|
complexite=int(f.complexite),
|
|
104
107
|
langage=resultat.langage,
|
|
105
108
|
fichier=chemin,
|
|
109
|
+
extraction=getattr(f, "extraction", "analyseur"),
|
|
106
110
|
)
|
|
107
111
|
)
|
|
108
112
|
except Exception:
|
|
@@ -41,6 +41,14 @@ from .base import AnalyseurBase
|
|
|
41
41
|
# Types de nœuds mot-clé (tels que la grammaire les normalise).
|
|
42
42
|
_TYPES_DECLARATION = {"theorem", "def", "abbrev", "instance", "structure", "inductive"}
|
|
43
43
|
|
|
44
|
+
# Kinds récupérables par le repli robuste : exactement ceux que
|
|
45
|
+
# l'analyseur compte (mots-clés SOURCE, avant normalisation —
|
|
46
|
+
# `lemma`→theorem et `class`→structure sont normalisés par la grammaire).
|
|
47
|
+
# `example` et `opaque` en sont exclus : le repli répare un angle mort,
|
|
48
|
+
# il n'étend pas le périmètre.
|
|
49
|
+
_KINDS_COMPTES = {"theorem", "lemma", "def", "abbrev", "instance",
|
|
50
|
+
"structure", "class", "inductive"}
|
|
51
|
+
|
|
44
52
|
# Enfants de `declaration` à sauter avant le mot-clé.
|
|
45
53
|
_PREFIXES_IGNORES = {"decl_modifiers", "attributes"}
|
|
46
54
|
|
|
@@ -76,17 +84,33 @@ class AnalyseurLean(AnalyseurBase):
|
|
|
76
84
|
"""
|
|
77
85
|
Analyseur fractal dédié à Lean 4 : déclarations de tête uniquement,
|
|
78
86
|
métriques calibrées pour le code de preuve.
|
|
87
|
+
|
|
88
|
+
Moteur d'extraction (2026-10-03, autonomie stricte) :
|
|
89
|
+
- "autonome" (défaut) : parseur_autonome (stdlib uniquement, zéro
|
|
90
|
+
dépendance externe) — source unique sur le chemin Lean ;
|
|
91
|
+
- "tree_sitter" : ancien chemin tree-sitter, conservé pour vérification
|
|
92
|
+
croisée optionnelle (désactivé par défaut).
|
|
79
93
|
"""
|
|
80
94
|
|
|
81
|
-
def __init__(self, fichier: str):
|
|
95
|
+
def __init__(self, fichier: str, moteur: str = "autonome"):
|
|
82
96
|
super().__init__(fichier)
|
|
97
|
+
self.moteur = moteur
|
|
83
98
|
self.tree = None
|
|
84
99
|
self.source: bytes = b""
|
|
85
100
|
self.lignes: List[str] = []
|
|
86
101
|
self.resultat = ResultatAnalyse(fichier=fichier, langage="lean")
|
|
87
102
|
|
|
88
103
|
def charger(self) -> "AnalyseurLean":
|
|
89
|
-
"""Charge
|
|
104
|
+
"""Charge le fichier. Le moteur autonome ne nécessite rien
|
|
105
|
+
d'externe ; le moteur tree_sitter lève ImportError explicite si
|
|
106
|
+
la grammaire manque."""
|
|
107
|
+
if self.moteur == "autonome":
|
|
108
|
+
with open(self.fichier, "r", encoding="utf-8", errors="replace") as f:
|
|
109
|
+
contenu = f.read()
|
|
110
|
+
self.lignes = contenu.splitlines()
|
|
111
|
+
self.source = contenu.encode("utf-8", errors="replace")
|
|
112
|
+
self._contenu = contenu
|
|
113
|
+
return self
|
|
90
114
|
try:
|
|
91
115
|
from tree_sitter_language_pack import get_parser
|
|
92
116
|
except ImportError as exc:
|
|
@@ -113,6 +137,9 @@ class AnalyseurLean(AnalyseurBase):
|
|
|
113
137
|
"""
|
|
114
138
|
Analyse : déclarations de tête, métriques, règles, oudjat.
|
|
115
139
|
|
|
140
|
+
Moteur "autonome" (défaut) : parseur proprietaire sans dépendance
|
|
141
|
+
externe. Moteur "tree_sitter" : ancien chemin (vérification croisée).
|
|
142
|
+
|
|
116
143
|
`complet=True` : comportement historique. `complet=False`
|
|
117
144
|
(phase 1) : noms, lignes, `complexite = nb_lignes` (PROXY explicite,
|
|
118
145
|
pas le proxy lignes+tactiques du mode complet) ; ni comptage de
|
|
@@ -120,6 +147,8 @@ class AnalyseurLean(AnalyseurBase):
|
|
|
120
147
|
(sentinelles : `profondeur_max=0`, `distance_fib=0.0`,
|
|
121
148
|
`phi_ratio=1.0`). L'oudjat est la déclaration la plus longue.
|
|
122
149
|
"""
|
|
150
|
+
if self.moteur == "autonome":
|
|
151
|
+
return self._analyser_autonome(complet=complet)
|
|
123
152
|
if self.tree is None:
|
|
124
153
|
self.charger()
|
|
125
154
|
racine = self.tree.root_node
|
|
@@ -134,13 +163,88 @@ class AnalyseurLean(AnalyseurBase):
|
|
|
134
163
|
self.resultat.nb_classes += 1
|
|
135
164
|
elif enfant.type in _COMMENTAIRES:
|
|
136
165
|
self.resultat.nb_commentaires += 1
|
|
166
|
+
# Durcissement 2026-10-02 : repli robuste — récupère les
|
|
167
|
+
# déclarations avalées par la récupération d'erreur tree-sitter
|
|
168
|
+
# (cause racine : `|expr|` en position de type, voir _repli_robuste).
|
|
169
|
+
self._repli_robuste()
|
|
137
170
|
if complet:
|
|
138
171
|
self._regle_fibonacci()
|
|
139
172
|
self._identifier_oudjat(complet=complet)
|
|
140
173
|
return self.resultat
|
|
141
174
|
|
|
142
175
|
# ────────────────────────────────────────────────────────
|
|
143
|
-
#
|
|
176
|
+
# MOTEUR AUTONOME (2026-10-03, autonomie stricte)
|
|
177
|
+
# ────────────────────────────────────────────────────────
|
|
178
|
+
|
|
179
|
+
def _analyser_autonome(self, complet: bool = True) -> ResultatAnalyse:
|
|
180
|
+
"""Analyse via le parseur autonome (stdlib uniquement).
|
|
181
|
+
|
|
182
|
+
Même contrat de sortie que le chemin tree-sitter : symboles
|
|
183
|
+
comptés = KINDS_COMPTES, complexite = nb_lignes + nb_tactiques
|
|
184
|
+
(mode complet) ou nb_lignes (phase 1, PROXY explicite).
|
|
185
|
+
Les avertissements du parseur deviennent des annotations WARNING
|
|
186
|
+
— jamais silencieux.
|
|
187
|
+
"""
|
|
188
|
+
from ..parseur_autonome import parse as parse_autonome, KINDS_COMPTES
|
|
189
|
+
if not hasattr(self, "_contenu"):
|
|
190
|
+
self.charger()
|
|
191
|
+
contenu = self._contenu
|
|
192
|
+
self.resultat.nb_lignes_total = len(self.lignes)
|
|
193
|
+
res = parse_autonome(contenu)
|
|
194
|
+
self.resultat.nb_commentaires = res.nb_commentaires
|
|
195
|
+
for d in res.declarations:
|
|
196
|
+
if d.kind not in KINDS_COMPTES:
|
|
197
|
+
continue
|
|
198
|
+
if d.nom.startswith("_"):
|
|
199
|
+
continue # définitions anonymes, comme avant
|
|
200
|
+
nb_lignes = d.ligne_fin - d.ligne_debut + 1
|
|
201
|
+
if not complet:
|
|
202
|
+
mesure = MetriqueFonction(
|
|
203
|
+
nom=d.nom,
|
|
204
|
+
ligne=d.ligne,
|
|
205
|
+
complexite=nb_lignes, # PROXY phase 1 (voir docstring analyser)
|
|
206
|
+
nb_args=0, # sentinelle : non calculé
|
|
207
|
+
nb_lignes=nb_lignes,
|
|
208
|
+
profondeur_max=0, # sentinelle : non calculée
|
|
209
|
+
distance_fib=0.0, # sentinelle : non calculée
|
|
210
|
+
phi_ratio=1.0, # sentinelle : non calculé
|
|
211
|
+
extraction="autonome",
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
mesure = MetriqueFonction(
|
|
215
|
+
nom=d.nom,
|
|
216
|
+
ligne=d.ligne,
|
|
217
|
+
complexite=nb_lignes + d.nb_tactiques,
|
|
218
|
+
nb_args=d.nb_args,
|
|
219
|
+
nb_lignes=nb_lignes,
|
|
220
|
+
profondeur_max=d.profondeur_by,
|
|
221
|
+
distance_fib=distance_fibonacci(nb_lignes),
|
|
222
|
+
phi_ratio=1.0, # Calculé après, quand la moyenne est connue
|
|
223
|
+
extraction="autonome",
|
|
224
|
+
)
|
|
225
|
+
self.resultat.fonctions.append(mesure)
|
|
226
|
+
if d.kind in ("structure", "class", "inductive"):
|
|
227
|
+
self.resultat.nb_classes += 1
|
|
228
|
+
# Avertissements du parseur → annotations bruyantes.
|
|
229
|
+
for av in res.avertissements:
|
|
230
|
+
extrait = self.lignes[av.ligne - 1].strip() \
|
|
231
|
+
if 0 < av.ligne <= len(self.lignes) else ""
|
|
232
|
+
self.resultat.annotations.append(
|
|
233
|
+
Annotation(
|
|
234
|
+
ligne=av.ligne,
|
|
235
|
+
message=f"PARSEUR AUTONOME : {av.message}",
|
|
236
|
+
niveau="WARNING",
|
|
237
|
+
extrait=extrait,
|
|
238
|
+
categorie="PARSEUR",
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
if complet:
|
|
242
|
+
self._regle_fibonacci()
|
|
243
|
+
self._identifier_oudjat(complet=complet)
|
|
244
|
+
return self.resultat
|
|
245
|
+
|
|
246
|
+
# ────────────────────────────────────────────────────────
|
|
247
|
+
# EXTRACTION DES DÉCLARATIONS (chemin tree_sitter)
|
|
144
248
|
# ────────────────────────────────────────────────────────
|
|
145
249
|
|
|
146
250
|
def _noeud_mot_cle(self, declaration):
|
|
@@ -219,9 +323,86 @@ class AnalyseurLean(AnalyseurBase):
|
|
|
219
323
|
)
|
|
220
324
|
|
|
221
325
|
# ────────────────────────────────────────────────────────
|
|
222
|
-
#
|
|
326
|
+
# REPLI ROBUSTE (durcissement 2026-10-02)
|
|
223
327
|
# ────────────────────────────────────────────────────────
|
|
224
328
|
|
|
329
|
+
def _repli_robuste(self) -> int:
|
|
330
|
+
"""Récupère les déclarations avalées par la récupération d'erreur.
|
|
331
|
+
|
|
332
|
+
Cause racine : la grammaire tree-sitter-lean confond les barres
|
|
333
|
+
`|expr|` (valeur absolue / norme) en position de type de retour
|
|
334
|
+
avec une alternative de filtrage `|` ; le nœud ERROR produit
|
|
335
|
+
avale ensuite les déclarations suivantes, qui deviennent
|
|
336
|
+
INVISIBLES à l'analyseur — silencieusement (constaté :
|
|
337
|
+
169 déclarations vues sur 261 dans scratch_65c_global.lean,
|
|
338
|
+
412 déclarations manquées sur 60/176 fichiers du dépôt Lean).
|
|
339
|
+
|
|
340
|
+
L'extracteur robuste (regex) est la source de vérité pour
|
|
341
|
+
l'EXISTENCE des symboles ; tree-sitter garde la main sur les
|
|
342
|
+
métriques fines quand il voit la déclaration. Les symboles
|
|
343
|
+
récupérés portent extraction="robuste_repli" et des métriques
|
|
344
|
+
PROXY honnêtes (complexite = nb_lignes du span, comme la
|
|
345
|
+
phase 1) — jamais de métriques inventées, jamais de silence.
|
|
346
|
+
|
|
347
|
+
Seuls les kinds déjà comptés par l'analyseur sont récupérés
|
|
348
|
+
(pas de `example`, pas de `opaque`) : le repli ne crée aucun
|
|
349
|
+
nouveau périmètre, il répare un angle mort.
|
|
350
|
+
|
|
351
|
+
Retourne le nombre de symboles récupérés. Le repli ne lève
|
|
352
|
+
jamais : en cas d'échec il ne fait rien (l'analyse principale
|
|
353
|
+
reste intacte).
|
|
354
|
+
"""
|
|
355
|
+
try:
|
|
356
|
+
from ..parseur_lean import extraire_declarations_robuste
|
|
357
|
+
code = self.source.decode("utf-8", errors="replace")
|
|
358
|
+
robustes = sorted(extraire_declarations_robuste(code),
|
|
359
|
+
key=lambda d: d.ligne)
|
|
360
|
+
# Référence de couverture : ce que la passe tree-sitter a
|
|
361
|
+
# VRAIMENT extrait (self.resultat.fonctions) — et non
|
|
362
|
+
# extraire_declarations(), qui ignore structure/inductive
|
|
363
|
+
# alors que l'analyseur les compte (doublons sinon).
|
|
364
|
+
vus = [(f.nom, f.ligne) for f in self.resultat.fonctions]
|
|
365
|
+
|
|
366
|
+
def couvert(d):
|
|
367
|
+
return any(n == d.nom and abs(l - d.ligne) <= 2
|
|
368
|
+
for (n, l) in vus)
|
|
369
|
+
|
|
370
|
+
manquants = [
|
|
371
|
+
d for d in robustes
|
|
372
|
+
if d.kind in _KINDS_COMPTES and not d.nom.startswith("_")
|
|
373
|
+
and not couvert(d)
|
|
374
|
+
]
|
|
375
|
+
if not manquants:
|
|
376
|
+
return 0
|
|
377
|
+
# Spans : chaque déclaration robuste va jusqu'à la suivante
|
|
378
|
+
# (tous kinds, comme le découpage par grep de _spans_symboles).
|
|
379
|
+
lignes_fin = {}
|
|
380
|
+
for i, d in enumerate(robustes):
|
|
381
|
+
fin = (robustes[i + 1].ligne - 1
|
|
382
|
+
if i + 1 < len(robustes) else len(self.lignes))
|
|
383
|
+
lignes_fin[(d.nom, d.ligne)] = max(d.ligne, fin)
|
|
384
|
+
recuperes = 0
|
|
385
|
+
for d in manquants:
|
|
386
|
+
fin = lignes_fin.get((d.nom, d.ligne), len(self.lignes))
|
|
387
|
+
span = max(1, fin - d.ligne + 1)
|
|
388
|
+
self.resultat.fonctions.append(MetriqueFonction(
|
|
389
|
+
nom=d.nom,
|
|
390
|
+
ligne=d.ligne,
|
|
391
|
+
complexite=span, # PROXY phase 1, explicite
|
|
392
|
+
nb_args=0, # sentinelle : non calculé
|
|
393
|
+
nb_lignes=span,
|
|
394
|
+
profondeur_max=0, # sentinelle : non calculée
|
|
395
|
+
distance_fib=distance_fibonacci(span),
|
|
396
|
+
phi_ratio=1.0, # sentinelle : non calculé
|
|
397
|
+
extraction="robuste_repli",
|
|
398
|
+
))
|
|
399
|
+
if d.kind in ("structure", "class", "inductive"):
|
|
400
|
+
self.resultat.nb_classes += 1
|
|
401
|
+
recuperes += 1
|
|
402
|
+
return recuperes
|
|
403
|
+
except Exception:
|
|
404
|
+
return 0
|
|
405
|
+
|
|
225
406
|
def _compter_tactiques(self, declaration) -> int:
|
|
226
407
|
"""Nombre de pas de tactique = enfants nommés directs des blocs `by`."""
|
|
227
408
|
total = 0
|
|
@@ -121,8 +121,12 @@ def analyseur_disponible(chemin: str, langage: Optional[str] = None) -> bool:
|
|
|
121
121
|
return False
|
|
122
122
|
if lang == "python":
|
|
123
123
|
return True
|
|
124
|
+
if lang == "lean":
|
|
125
|
+
# Parseur autonome (stdlib uniquement, 2026-10-03) : Lean est
|
|
126
|
+
# toujours analysable, sans aucune dépendance externe.
|
|
127
|
+
return True
|
|
124
128
|
ext = os.path.splitext(chemin)[1].lower()
|
|
125
|
-
if ext in EXTENSIONS_ANALYSEUR_DEDIE
|
|
129
|
+
if ext in EXTENSIONS_ANALYSEUR_DEDIE:
|
|
126
130
|
# Analyseur dédié : le pack seul ne suffit pas, il faut la grammaire.
|
|
127
131
|
return treesitter_disponible() and _grammaire_lean_ok()
|
|
128
132
|
return treesitter_disponible()
|
|
@@ -149,14 +153,16 @@ def obtenir_analyseur(fichier: str, langage: Optional[str] = None) -> AnalyseurB
|
|
|
149
153
|
return AnalyseurPython(fichier)
|
|
150
154
|
|
|
151
155
|
ext = os.path.splitext(fichier)[1].lower()
|
|
152
|
-
if ext in EXTENSIONS_ANALYSEUR_DEDIE or lang == "lean":
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
156
|
+
if ext in EXTENSIONS_ANALYSEUR_DEDIE or lang == "lean":
|
|
157
|
+
# Lean : parseur autonome par défaut (zéro dépendance externe).
|
|
158
|
+
# Le moteur tree_sitter reste disponible en vérification croisée
|
|
159
|
+
# optionnelle : AnalyseurLean(fichier, moteur="tree_sitter").
|
|
160
|
+
return _analyseur_dedie(ext)(fichier)
|
|
161
|
+
|
|
162
|
+
if not treesitter_disponible():
|
|
163
|
+
raise ImportError(
|
|
164
|
+
"Analyse non supportée : `tree-sitter-language-pack` indisponible "
|
|
165
|
+
"(installez `pip install phi-complexity[multilang]`)."
|
|
166
|
+
)
|
|
161
167
|
from .treesitter_generic import AnalyseurTreeSitter
|
|
162
168
|
return AnalyseurTreeSitter(fichier, ts_langage=lang)
|
|
@@ -151,6 +151,25 @@ class AnalyseurTreeSitter(AnalyseurBase):
|
|
|
151
151
|
self.charger()
|
|
152
152
|
racine = self.tree.root_node
|
|
153
153
|
self.resultat.nb_lignes_total = len(self.lignes)
|
|
154
|
+
# ────────────────────────────────────────────────────────
|
|
155
|
+
# VERROUILLAGE « zéro tree-sitter silencieux » (2026-10-03) :
|
|
156
|
+
# un arbre avec nœuds ERROR signifie récupération d'erreur —
|
|
157
|
+
# des symboles peuvent manquer SANS aucun signal sinon.
|
|
158
|
+
# On l'annonce en annotation CRITICAL explicite (jamais de
|
|
159
|
+
# silence) ; on ne tente pas de réparer la grammaire externe.
|
|
160
|
+
# ────────────────────────────────────────────────────────
|
|
161
|
+
try:
|
|
162
|
+
if racine.has_error:
|
|
163
|
+
self._annoter(
|
|
164
|
+
1,
|
|
165
|
+
"⚠️ TREE-SITTER DÉGRADÉ : l'arbre syntaxique contient "
|
|
166
|
+
"des nœuds d'erreur — des symboles peuvent manquer "
|
|
167
|
+
"silencieusement dans cette analyse.",
|
|
168
|
+
"CRITICAL",
|
|
169
|
+
"SOUVERAINETE",
|
|
170
|
+
)
|
|
171
|
+
except Exception:
|
|
172
|
+
pass # la garde ne doit jamais casser l'analyse
|
|
154
173
|
if complet:
|
|
155
174
|
self._compter_elements_globaux(racine)
|
|
156
175
|
self._analyser_fonctions(racine)
|
|
@@ -143,6 +143,14 @@ JUSTIFICATIONS = {
|
|
|
143
143
|
"croyance montre sa chaîne, pas de boîte noire.",
|
|
144
144
|
"sert": "instrument",
|
|
145
145
|
},
|
|
146
|
+
"parseur_autonome": {
|
|
147
|
+
"but": "Parseur Lean 4 proprietaire de phi-complexity (stdlib "
|
|
148
|
+
"uniquement, zero dependance externe) : lexer + descente "
|
|
149
|
+
"recursive sur les en-tetes + delimitation exacte des corps. "
|
|
150
|
+
"Remplace tree-sitter sur tout le chemin Lean depuis "
|
|
151
|
+
"l'autonomie stricte du 2026-10-03.",
|
|
152
|
+
"sert": "instrument",
|
|
153
|
+
},
|
|
146
154
|
"piste_sorry": {
|
|
147
155
|
"but": "Piste d'un sorry à travers Lean 4 : inventaire rigoureux "
|
|
148
156
|
"(commentaires et chaînes exclus), graphe d'imports, chantiers "
|
|
@@ -17,6 +17,9 @@ class MetriqueFonction:
|
|
|
17
17
|
profondeur_max: int # Imbrication maximale
|
|
18
18
|
distance_fib: float # Éloignement de la séquence naturelle
|
|
19
19
|
phi_ratio: float # Rapport complexité/moyenne (idéal: φ)
|
|
20
|
+
extraction: str = "analyseur" # "analyseur" ou "robuste_repli"
|
|
21
|
+
# (durcissement 2026-10-02 : symboles récupérés par l'extracteur
|
|
22
|
+
# robuste quand tree-sitter les a avalés — métriques = proxy lignes)
|
|
20
23
|
|
|
21
24
|
|
|
22
25
|
@dataclass
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
oracle.py — Traces d'oracle pour phi-complexity (v0.
|
|
2
|
+
oracle.py — Traces d'oracle pour phi-complexity (v0.14.1, opt-in EFT).
|
|
3
3
|
|
|
4
4
|
Un oracle n'est pas un devin : c'est une chaîne de raisonnement EXHIBÉE.
|
|
5
5
|
Chaque mise à jour de croyance enregistre :
|