vibetodev 1.0.0__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.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: vibetodev
3
+ Version: 1.0.0
4
+ Summary: Transforme n'importe quel projet vibecode en parcours d'apprentissage
5
+ Author: ConnectPro
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/connectpro/vibetodev
8
+ Project-URL: Source, https://github.com/connectpro/vibetodev
9
+ Keywords: learning,education,code-analysis,obsidian,ast
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
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 :: Education
21
+ Classifier: Topic :: Software Development :: Code Generators
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ Dynamic: requires-python
25
+
26
+ # VibeToDev
27
+
28
+ Transforme n'importe quel projet Python vibecodé en parcours d'apprentissage structuré.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install vibetodev
34
+ ```
35
+
36
+ Ou depuis les sources :
37
+
38
+ ```bash
39
+ git clone https://github.com/connectpro/vibetodev
40
+ cd vibetodev
41
+ pip install .
42
+ ```
43
+
44
+ ## Utilisation
45
+
46
+ ```bash
47
+ # Scanner un projet et generer un vault Obsidian
48
+ vibetodev scan /chemin/vers/mon_projet --output /chemin/vers/mon_vault
49
+
50
+ # Valider un exercice
51
+ vibetodev check mon_exercice.py
52
+
53
+ # Initialiser vibetodev dans un projet
54
+ vibetodev init /chemin/vers/mon_projet
55
+ ```
56
+
57
+ ## Comment ca marche
58
+
59
+ 1. **Scan** — Analyse AST de tous les fichiers Python, extrait 50+ concepts avec leur position exacte
60
+ 2. **Categorisation** — 9 modules pedagogiques (variables, fonctions, classes, etc.)
61
+ 3. **Generation** — Cree un vault Obsidian avec lecons, exemples de code, et exercices
62
+ 4. **Validation** — Execute et verifie les exercices sans donner la solution
63
+
64
+ ## Licence
65
+
66
+ MIT
@@ -0,0 +1,41 @@
1
+ # VibeToDev
2
+
3
+ Transforme n'importe quel projet Python vibecodé en parcours d'apprentissage structuré.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install vibetodev
9
+ ```
10
+
11
+ Ou depuis les sources :
12
+
13
+ ```bash
14
+ git clone https://github.com/connectpro/vibetodev
15
+ cd vibetodev
16
+ pip install .
17
+ ```
18
+
19
+ ## Utilisation
20
+
21
+ ```bash
22
+ # Scanner un projet et generer un vault Obsidian
23
+ vibetodev scan /chemin/vers/mon_projet --output /chemin/vers/mon_vault
24
+
25
+ # Valider un exercice
26
+ vibetodev check mon_exercice.py
27
+
28
+ # Initialiser vibetodev dans un projet
29
+ vibetodev init /chemin/vers/mon_projet
30
+ ```
31
+
32
+ ## Comment ca marche
33
+
34
+ 1. **Scan** — Analyse AST de tous les fichiers Python, extrait 50+ concepts avec leur position exacte
35
+ 2. **Categorisation** — 9 modules pedagogiques (variables, fonctions, classes, etc.)
36
+ 3. **Generation** — Cree un vault Obsidian avec lecons, exemples de code, et exercices
37
+ 4. **Validation** — Execute et verifie les exercices sans donner la solution
38
+
39
+ ## Licence
40
+
41
+ MIT
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.backends._legacy:_Backend"
4
+
5
+ [project]
6
+ name = "vibetodev"
7
+ version = "1.0.0"
8
+ description = "Transforme n'importe quel projet vibecode en parcours d'apprentissage"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ authors = [
12
+ {name = "ConnectPro"}
13
+ ]
14
+ keywords = ["learning", "education", "code-analysis", "obsidian", "ast"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Education",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.8",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Topic :: Education",
27
+ "Topic :: Software Development :: Code Generators",
28
+ ]
29
+ requires-python = ">=3.8"
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/connectpro/vibetodev"
33
+ Source = "https://github.com/connectpro/vibetodev"
34
+
35
+ [project.scripts]
36
+ vibetodev = "vibetodev.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,38 @@
1
+ """Transforme n'importe quel projet vibecode en parcours d'apprentissage."""
2
+ from setuptools import setup, find_packages
3
+ from pathlib import Path
4
+
5
+ this_dir = Path(__file__).parent
6
+ long_description = (this_dir / "README.md").read_text(encoding="utf-8") if (this_dir / "README.md").exists() else ""
7
+
8
+ setup(
9
+ name="vibetodev",
10
+ version="1.0.0",
11
+ description="Transforme n'importe quel projet vibecode en parcours d'apprentissage",
12
+ long_description=long_description,
13
+ long_description_content_type="text/markdown",
14
+ author="ConnectPro",
15
+ license="MIT",
16
+ keywords="learning education code-analysis obsidian ast",
17
+ classifiers=[
18
+ "Development Status :: 4 - Beta",
19
+ "Intended Audience :: Developers",
20
+ "Intended Audience :: Education",
21
+ "License :: OSI Approved :: MIT License",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.8",
24
+ "Programming Language :: Python :: 3.9",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Education",
29
+ "Topic :: Software Development :: Code Generators",
30
+ ],
31
+ packages=find_packages(),
32
+ entry_points={
33
+ "console_scripts": [
34
+ "vibetodev=vibetodev.cli:main",
35
+ ],
36
+ },
37
+ python_requires=">=3.8",
38
+ )
@@ -0,0 +1,5 @@
1
+ """
2
+ vibetodev — Transforme n'importe quel projet vibecode en parcours d'apprentissage.
3
+ """
4
+
5
+ __version__ = "1.0.0"
@@ -0,0 +1,6 @@
1
+ """python -m vibetodev"""
2
+
3
+ from .cli import main
4
+
5
+ if __name__ == "__main__":
6
+ main()
@@ -0,0 +1,124 @@
1
+ """
2
+ Categorise les concepts extraits en modules pedagogiques.
3
+ Assigne un niveau (debutant/intermediaire/avance) et un ordre.
4
+ """
5
+
6
+ from collections import defaultdict
7
+
8
+ # Mapping concept -> module
9
+ CONCEPT_MODULE = {
10
+ # Module 1 : Variables
11
+ "type_str": 1, "type_int": 1, "type_bool": 1, "type_float": 1,
12
+ "type_none": 1, "type_bytes": 1, "chaine": 1,
13
+ "assignation": 1, "assignation_multiple": 1, "assignation_typee": 1,
14
+ "assignation_augmentee": 1,
15
+
16
+ # Module 2 : Fonctions
17
+ "fonction_def": 2, "fonction_async": 2, "return": 2, "yield": 2,
18
+ "type_hint_param": 2, "type_hint_return": 2, "docstring": 2,
19
+ "decorateur": 2, "lambda": 2,
20
+
21
+ # Module 3 : Controle
22
+ "condition_if": 3, "condition_elif": 3, "condition_else": 3,
23
+ "boucle_for": 3, "boucle_while": 3, "boucle_async_for": 3,
24
+ "break": 3, "continue": 3, "ternaire": 3,
25
+
26
+ # Module 4 : Gestion d'erreurs
27
+ "try_except": 4, "try_finally": 4, "raise": 4,
28
+ "context_manager_with": 4, "context_manager_async_with": 4,
29
+
30
+ # Module 5 : Collections
31
+ "type_list": 5, "type_dict": 5, "type_set": 5, "type_tuple": 5,
32
+ "comprehension_liste": 5, "comprehension_dict": 5, "comprehension_set": 5,
33
+ "assignation_index": 5, "slice": 5,
34
+
35
+ # Module 6 : Operations
36
+ "appel_fonction": 6, "appel_methode": 6,
37
+ "operateur_comparaison": 6, "operateur_logique": 6,
38
+ "fstring": 6, "binop_Add": 6, "binop_Sub": 6, "binop_Mult": 6,
39
+ "binop_Div": 6, "unpacking": 6, "walrus_operator": 6,
40
+
41
+ # Module 7 : Imports
42
+ "import_module": 7, "import_from": 7,
43
+
44
+ # Module 8 : Classes et Objets
45
+ "class_def": 8,
46
+ "assignation_attr": 8,
47
+
48
+ # Module 9 : Programmation avancee
49
+ # (tout ce qui n'a pas encore ete categorise)
50
+ }
51
+
52
+ NOMS_MODULES = {
53
+ 1: "01_Variables_et_Types",
54
+ 2: "02_Fonctions",
55
+ 3: "03_Structures_de_Controle",
56
+ 4: "04_Gestion_d_Erreurs",
57
+ 5: "05_Collections",
58
+ 6: "06_Operations_et_Appels",
59
+ 7: "07_Imports_et_Modules",
60
+ 8: "08_Classes_et_Objets",
61
+ 9: "09_Concepts_Avances",
62
+ }
63
+
64
+ TITRES_MODULES = {
65
+ 1: "Variables et types de donnees",
66
+ 2: "Les fonctions",
67
+ 3: "Structures de controle",
68
+ 4: "Gestion d'erreurs",
69
+ 5: "Collections (listes, dicts, sets, tuples)",
70
+ 6: "Operations et appels",
71
+ 7: "Imports et modules",
72
+ 8: "Classes et objets",
73
+ 9: "Concepts avances (async, decorateurs, etc.)",
74
+ }
75
+
76
+ NIVEAU_CONCEPT = {
77
+ "type_str": "debutant", "type_int": "debutant", "type_bool": "debutant",
78
+ "type_float": "debutant", "type_none": "debutant", "chaine": "debutant",
79
+ "assignation": "debutant", "assignation_multiple": "debutant",
80
+ "assignation_augmentee": "debutant",
81
+ "fonction_def": "debutant", "return": "debutant",
82
+ "docstring": "debutant",
83
+ "condition_if": "debutant", "condition_else": "debutant",
84
+ "boucle_for": "debutant",
85
+ "type_list": "debutant", "type_dict": "debutant",
86
+ "fstring": "debutant",
87
+ "import_module": "debutant", "import_from": "debutant",
88
+ "appel_fonction": "debutant",
89
+ "appel_methode": "debutant",
90
+ "operateur_comparaison": "debutant",
91
+ "operateur_logique": "debutant",
92
+ "type_float": "debutant",
93
+ "break": "debutant", "continue": "debutant",
94
+ "condition_elif": "intermediaire",
95
+ "assignation_index": "intermediaire",
96
+ "type_set": "intermediaire", "type_tuple": "intermediaire",
97
+ "assignation_typee": "intermediaire",
98
+ "type_hint_param": "intermediaire",
99
+ "try_except": "intermediaire",
100
+ "context_manager_with": "intermediaire",
101
+ "comprehension_liste": "intermediaire",
102
+ "decorateur": "avance",
103
+ "lambda": "avance",
104
+ "yield": "avance",
105
+ "class_def": "avance",
106
+ "comprehension_dict": "avance",
107
+ "walrus_operator": "avance",
108
+ "fonction_async": "avance",
109
+ "boucle_async_for": "avance",
110
+ "context_manager_async_with": "avance",
111
+ }
112
+
113
+
114
+ def categorize_concepts(concepts_bruts):
115
+ """
116
+ Prend {concept: [occurences]} et retourne {module_id: {concept: occurences}}.
117
+ """
118
+ modules = defaultdict(lambda: defaultdict(list))
119
+
120
+ for concept, occs in concepts_bruts.items():
121
+ module_id = CONCEPT_MODULE.get(concept, 9) # defaut: avance
122
+ modules[module_id][concept] = occs
123
+
124
+ return dict(sorted(modules.items()))
@@ -0,0 +1,132 @@
1
+ """
2
+ CLI principale de vibetodev.
3
+ Usage:
4
+ vibetodev scan <chemin_projet> [--output <chemin_vault>]
5
+ vibetodev check <fichier_exercice>
6
+ vibetodev init <chemin_projet>
7
+ vibetodev serve
8
+ """
9
+
10
+ import argparse
11
+ import sys
12
+ import os
13
+ from pathlib import Path
14
+
15
+ from .scanner import scan_project
16
+ from .categorizer import categorize_concepts
17
+ from .generator import generate_vault
18
+ from .validator import validate_exercise
19
+
20
+
21
+ def cmd_scan(args):
22
+ """Analyse un projet et genere un vault Obsidian."""
23
+ source = Path(args.source).resolve()
24
+ if not source.exists():
25
+ print(f"Erreur: '{source}' n'existe pas.")
26
+ return 1
27
+
28
+ output = Path(args.output).resolve() if args.output else source / ".vibetodev-vault"
29
+
30
+ print(f"Scan de {source}...")
31
+ concepts = scan_project(source, args.recursive)
32
+
33
+ if not concepts:
34
+ print("Aucun concept trouve.")
35
+ return 1
36
+
37
+ total = sum(len(v) for v in concepts.values())
38
+ print(f"-> {len(concepts)} concepts distincts, {total} occurrences")
39
+
40
+ print("Categorisation...")
41
+ modules = categorize_concepts(concepts)
42
+
43
+ print("Generation du vault Obsidian...")
44
+ generate_vault(modules, concepts, output)
45
+
46
+ print(f"\nVault cree dans: {output}")
47
+ print(f"Ouvre ce dossier dans Obsidian pour voir les lecons.")
48
+ return 0
49
+
50
+
51
+ def cmd_check(args):
52
+ """Valide un exercice."""
53
+ fichier = Path(args.fichier).resolve()
54
+ if not fichier.exists():
55
+ print(f"Erreur: '{fichier}' introuvable.")
56
+ return 1
57
+
58
+ result = validate_exercise(fichier, args.concept)
59
+ if result["status"] == "success":
60
+ print("RESULTAT: VALIDE")
61
+ for r in result.get("reussites", []):
62
+ print(f" [OK] {r}")
63
+ print(result.get("message", ""))
64
+ return 0
65
+ elif result["status"] == "partial":
66
+ print("RESULTAT: PARTIEL")
67
+ for r in result.get("reussites", []):
68
+ print(f" [OK] {r}")
69
+ for e in result.get("erreurs", []):
70
+ print(f" [A CORRIGER] {e}")
71
+ print(f"Indice: {result.get('hint', '')}")
72
+ return 1
73
+ else:
74
+ print("RESULTAT: ERREUR")
75
+ print(result.get("message", ""))
76
+ print(f"Indice: {result.get('hint', '')}")
77
+ return 1
78
+
79
+
80
+ def cmd_init(args):
81
+ """Initialise un projet pour vibetodev."""
82
+ source = Path(args.source).resolve()
83
+ if not source.exists():
84
+ print(f"Erreur: '{source}' n'existe pas.")
85
+ return 1
86
+
87
+ vibetodev_dir = source / ".vibetodev"
88
+ vibetodev_dir.mkdir(exist_ok=True)
89
+
90
+ print(f"VibeToDev initialise dans {source}")
91
+ print(f"Fichier de config: {vibetodev_dir / 'config.json'}")
92
+ print(f"\nProchaine etape: vibetodev scan {source}")
93
+ return 0
94
+
95
+
96
+ def main():
97
+ parser = argparse.ArgumentParser(
98
+ prog="vibetodev",
99
+ description="Transforme un projet vibecode en parcours d'apprentissage",
100
+ )
101
+ parser.add_argument("--version", action="version", version="vibetodev 1.0.0")
102
+
103
+ sub = parser.add_subparsers(dest="command", help="Commande")
104
+
105
+ # scan
106
+ p_scan = sub.add_parser("scan", help="Analyser un projet et generer un vault")
107
+ p_scan.add_argument("source", help="Dossier du projet a analyser")
108
+ p_scan.add_argument("-o", "--output", help="Dossier de sortie du vault (defaut: <source>/.vibetodev-vault)")
109
+ p_scan.add_argument("-r", "--recursive", action="store_true", default=True, help="Scan recursif (defaut: True)")
110
+ p_scan.set_defaults(func=cmd_scan)
111
+
112
+ # check
113
+ p_check = sub.add_parser("check", help="Valider un exercice")
114
+ p_check.add_argument("fichier", help="Fichier exercice a valider")
115
+ p_check.add_argument("--concept", help="Concept a valider (optionnel)")
116
+ p_check.set_defaults(func=cmd_check)
117
+
118
+ # init
119
+ p_init = sub.add_parser("init", help="Initialiser vibetodev dans un projet")
120
+ p_init.add_argument("source", help="Dossier du projet")
121
+ p_init.set_defaults(func=cmd_init)
122
+
123
+ args = parser.parse_args()
124
+ if not args.command:
125
+ parser.print_help()
126
+ return 1
127
+
128
+ return args.func(args)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ sys.exit(main())
@@ -0,0 +1,240 @@
1
+ """
2
+ Genere un vault Obsidian complet avec lecons, exercices et liens vers le code.
3
+ """
4
+
5
+ import os
6
+ from pathlib import Path
7
+ from collections import defaultdict
8
+
9
+ from .categorizer import NOMS_MODULES, TITRES_MODULES, NIVEAU_CONCEPT
10
+
11
+ EXPLICATIONS = {
12
+ "type_str": "Une **chaine** (str) est du texte entre guillemets.",
13
+ "type_int": "Un **entier** (int) est un nombre sans virgule.",
14
+ "type_bool": "Un **booleen** (bool) vaut True ou False.",
15
+ "type_float": "Un **float** est un nombre decimal.",
16
+ "type_none": "**None** represente l'absence de valeur.",
17
+ "chaine": "Une **chaine vide** \"\" est une chaine sans caractere.",
18
+ "assignation": "L'**assignation** (=) stocke une valeur dans une variable.",
19
+ "assignation_multiple": "L'**assignation multiple** (=) assigne plusieurs variables en une ligne.",
20
+ "assignation_typee": "L'**assignation typee** (=) avec annotation de type.",
21
+ "assignation_augmentee": "L'assignation **augmentee** (+=, -=, etc.) combine operation et assignation.",
22
+ "fonction_def": "Une **fonction** est un bloc de code reutilisable defini avec `def`.",
23
+ "fonction_async": "Une **fonction asynchrone** utilise `async def`.",
24
+ "return": "**return** renvoie une valeur depuis une fonction.",
25
+ "yield": "**yield** transforme une fonction en generateur.",
26
+ "type_hint_param": "Les **type hints** (`param: type`) indiquent le type attendu.",
27
+ "type_hint_return": "Le **type hint de retour** (`-> type`) indique le type de retour.",
28
+ "docstring": "Une **docstring** (\"""...\""") decrit ce que fait la fonction.",
29
+ "decorateur": "Un **decorateur** (@) ajoute un comportement a une fonction.",
30
+ "lambda": "Une **lambda** est une fonction anonyme courte.",
31
+ "condition_if": "**if** execute un bloc si la condition est vraie.",
32
+ "condition_elif": "**elif** teste une autre condition.",
33
+ "condition_else": "**else** s'execute si tout est faux.",
34
+ "boucle_for": "**for** parcourt chaque element d'une collection.",
35
+ "boucle_while": "**while** repete tant que la condition est vraie.",
36
+ "boucle_async_for": "**async for** parcourt un iterateur asynchrone.",
37
+ "break": "**break** sort de la boucle immediatement.",
38
+ "continue": "**continue** passe a l'iteration suivante.",
39
+ "ternaire": "Le **ternaire** (`x if cond else y`) est un if en une ligne.",
40
+ "try_except": "**try/except** capture les erreurs sans planter.",
41
+ "try_finally": "**finally** s'execute toujours, erreur ou pas.",
42
+ "raise": "**raise** declenche une erreur volontairement.",
43
+ "context_manager_with": "**with** gere automatiquement les ressources.",
44
+ "context_manager_async_with": "**async with** pour les contextes asynchrones.",
45
+ "type_list": "Une **liste** `[]` est une collection ordonnee et modifiable.",
46
+ "type_dict": "Un **dict** `{}` associe des cles a des valeurs.",
47
+ "type_set": "Un **set** `{}` est un ensemble sans doublon.",
48
+ "type_tuple": "Un **tuple** `()` est immuable.",
49
+ "comprehension_liste": "Une **comprehension** cree une liste en une ligne.",
50
+ "comprehension_dict": "Une **comprehension** cree un dict en une ligne.",
51
+ "comprehension_set": "Une **comprehension** cree un set en une ligne.",
52
+ "assignation_index": "Modifie un element par son index ou sa cle.",
53
+ "slice": "Le **slicing** `[debut:fin:pas]` extrait une portion.",
54
+ "appel_fonction": "**appel de fonction** () execute une fonction.",
55
+ "appel_methode": "Une **methode** . est une fonction attachee a un objet.",
56
+ "operateur_comparaison": "Compare avec ==, !=, <, >, in, etc.",
57
+ "operateur_logique": "Combine avec **and**, **or**, **not**.",
58
+ "fstring": "Les **f-strings** f\"...\" integrent des variables.",
59
+ "import_module": "**import module** importe tout un module.",
60
+ "import_from": "**from module import** importe specifiquement.",
61
+ "class_def": "Une **classe** definit un nouveau type d'objet.",
62
+ "assignation_attr": "Modifie un attribut d'objet (`objet.attr = ...`).",
63
+ "unpacking": "L'**unpacking** (`*args`, `**kwargs`) decompose des sequences.",
64
+ "walrus_operator": "L'**operateur morse** (:=) assigne dans une expression.",
65
+ }
66
+
67
+
68
+ def generate_vault(modules, concepts_bruts, output_dir):
69
+ """Genere le vault Obsidian dans output_dir."""
70
+ output = Path(output_dir)
71
+ output.mkdir(parents=True, exist_ok=True)
72
+ obsidian_dir = output / ".obsidian"
73
+ obsidian_dir.mkdir(exist_ok=True)
74
+
75
+ _generate_index(modules, concepts_bruts, output)
76
+ _generate_obsidian_config(obsidian_dir)
77
+
78
+ for module_id, concepts in modules.items():
79
+ _generate_module(module_id, concepts, output)
80
+
81
+ print(f"Vault genere dans {output}/")
82
+ print(f" -> {len(modules)} modules")
83
+ print(f" -> {sum(len(c) for c in concepts_bruts.values())} concepts au total")
84
+
85
+
86
+ def _generate_obsidian_config(obsidian_dir):
87
+ """Cree la config minimale Obsidian."""
88
+ core = {
89
+ "file-explorer": True, "global-search": True, "switcher": True,
90
+ "graph": True, "backlink": True, "outgoing-link": True,
91
+ "tag-pane": True, "page-preview": True, "templates": True,
92
+ "command-palette": True, "outline": True, "word-count": True,
93
+ "daily-notes": False,
94
+ }
95
+ with open(obsidian_dir / "core-plugins.json", "w", encoding="utf-8") as f:
96
+ import json
97
+ json.dump(core, f, indent=2)
98
+
99
+ app = {}
100
+ with open(obsidian_dir / "app.json", "w", encoding="utf-8") as f:
101
+ json.dump(app, f, indent=2)
102
+
103
+
104
+ def _generate_index(modules, concepts_bruts, output):
105
+ """Genere la page d'accueil du vault."""
106
+ path = output / "Index.md"
107
+ total_concepts = len(concepts_bruts)
108
+ total_occ = sum(len(v) for v in concepts_bruts.values())
109
+
110
+ with open(path, "w", encoding="utf-8") as f:
111
+ f.write("# VibeToDev — Parcours d'apprentissage\n\n")
112
+ f.write("Vault genere automatiquement par analyse AST.\n\n")
113
+ f.write(f"- **{total_concepts} concepts** distincts\n")
114
+ f.write(f"- **{total_occ} occurrences** dans le code\n\n")
115
+
116
+ f.write("## Modules\n\n")
117
+ f.write("| Module | Contenu |\n")
118
+ f.write("|--------|--------|\n")
119
+ for mid in sorted(modules.keys()):
120
+ nom = NOMS_MODULES.get(mid, f"{mid:02d}")
121
+ titre = TITRES_MODULES.get(mid, f"Module {mid}")
122
+ concepts_count = sum(len(v) for v in modules[mid].values())
123
+ f.write(f"| [[{nom}/Lecon|Module {mid}]] | {titre} ({concepts_count} occ) |\n")
124
+
125
+ f.write("\n## Tous les concepts\n\n")
126
+ f.write("| Concept | Niveau | Occurrences |\n")
127
+ f.write("|---------|--------|------------|\n")
128
+ for concept in sorted(concepts_bruts.keys()):
129
+ niveau = NIVEAU_CONCEPT.get(concept, "intermediaire")
130
+ occ = len(concepts_bruts[concept])
131
+ f.write(f"| {concept} | {niveau} | {occ} |\n")
132
+
133
+
134
+ def _generate_module(module_id, concepts, output):
135
+ """Genere la lecon pour un module."""
136
+ nom_dossier = NOMS_MODULES.get(module_id, f"{module_id:02d}")
137
+ titre = TITRES_MODULES.get(module_id, f"Module {module_id}")
138
+ dossier = output / nom_dossier
139
+ dossier.mkdir(exist_ok=True)
140
+
141
+ path = dossier / "Lecon.md"
142
+ total_occ_module = sum(len(v) for v in concepts.values())
143
+
144
+ with open(path, "w", encoding="utf-8") as f:
145
+ f.write(f"# Module {module_id} : {titre}\n\n")
146
+ f.write(f"> {total_occ_module} occurrences dans le projet\n\n")
147
+
148
+ for concept in sorted(concepts.keys()):
149
+ occs = concepts[concept]
150
+ nom_concept = concept.replace("_", " ").title()
151
+ explication = EXPLICATIONS.get(concept, "")
152
+ niveau = NIVEAU_CONCEPT.get(concept, "intermediaire")
153
+
154
+ f.write(f"## {nom_concept}\n")
155
+ f.write(f"_{len(occs)} occurrences | Niveau: {niveau}_\n\n")
156
+
157
+ if explication:
158
+ f.write(f"{explication}\n\n")
159
+
160
+ # Exemples de code
161
+ f.write("### Dans le code\n\n")
162
+ for i, occ in enumerate(occs[:3]):
163
+ f.write(f"**Exemple {i+1}** — `{occ['fichier']}:{occ['ligne']}`\n\n")
164
+ f.write("```python\n")
165
+ f.write(f"{occ['code']}\n")
166
+ f.write("```\n\n")
167
+
168
+ # Autres occurrences
169
+ if len(occs) > 3:
170
+ par_fichier = defaultdict(list)
171
+ for occ in occs[3:]:
172
+ par_fichier[occ["fichier"]].append(occ["ligne"])
173
+ f.write(f"**{len(occs) - 3} autres occurrences :**\n\n")
174
+ for fichier, lignes in sorted(par_fichier.items()):
175
+ lignes_str = ", ".join(str(l) for l in lignes[:5])
176
+ if len(lignes) > 5:
177
+ lignes_str += f", ... ({len(lignes) - 5} de plus)"
178
+ f.write(f"- `{fichier}` : lignes {lignes_str}\n")
179
+ f.write("\n")
180
+
181
+ # Mini-exercice genere
182
+ f.write("### Mini-exercice\n\n")
183
+ f.write("```python\n")
184
+ f.write(_generer_exercice(concept))
185
+ f.write("\n```\n\n")
186
+
187
+ f.write("---\n\n")
188
+
189
+ # Navigation
190
+ f.write("## Navigation\n\n")
191
+ if module_id > 1:
192
+ prev = NOMS_MODULES.get(module_id - 1)
193
+ f.write(f"- [[../{prev}/Lecon|Module {module_id - 1}]]\n")
194
+ if module_id < 9:
195
+ next_mod = NOMS_MODULES.get(module_id + 1)
196
+ f.write(f"- [[../{next_mod}/Lecon|Module {module_id + 1}]]\n")
197
+ f.write("- [[Index|Retour a l'accueil]]\n")
198
+
199
+
200
+ def _generer_exercice(concept):
201
+ """Genere un mini-exercice adapte au concept."""
202
+ exercices = {
203
+ "type_str": 'nom = "VotrePrenom"\nprint(type(nom))\nprint(nom.upper())',
204
+ "type_int": 'age = 25\nannee = 2026 - age\nprint(annee)',
205
+ "type_bool": 'est_actif = True\nif est_actif:\n print("Actif")',
206
+ "type_float": 'prix = 19.99\ntva = prix * 0.2\nprint(tva)',
207
+ "type_none": 'resultat = None\nif resultat is None:\n print("Pas de resultat")',
208
+ "assignation": 'nom = "Sophie"\nage = 28\nprint(nom, age)',
209
+ "fonction_def": 'def saluer(nom):\n return f"Bonjour {nom}"\nprint(saluer("Sophie"))',
210
+ "return": 'def addition(a, b):\n return a + b\nprint(addition(3, 4))',
211
+ "condition_if": 'note = 15\nif note >= 10:\n print("Reussi")',
212
+ "boucle_for": 'noms = ["Alice", "Bob", "Charlie"]\nfor n in noms:\n print(n)',
213
+ "try_except": 'try:\n x = 1 / 0\nexcept ZeroDivisionError:\n print("Division par zero")',
214
+ "type_list": 'fruits = ["pomme", "banane"]\nfruits.append("kiwi")\nprint(fruits[0])',
215
+ "type_dict": 'profil = {"nom": "Alice", "age": 30}\nprint(profil["nom"])',
216
+ "type_set": 'a = {"x", "y"}\nb = {"y", "z"}\nprint(a & b) # intersection',
217
+ "type_tuple": 'coords = (48.85, 2.35)\nprint(coords[0])',
218
+ "comprehension_liste": 'nombres = [1, 2, 3, 4, 5]\npairs = [n for n in nombres if n % 2 == 0]\nprint(pairs)',
219
+ "appel_fonction": 'print("Hello")\nlen([1, 2, 3])',
220
+ "appel_methode": 'texte = "Bonjour"\nprint(texte.lower())\nprint(texte.upper())',
221
+ "fstring": 'nom = "Sophie"\nprint(f"Bonjour {nom}")',
222
+ "import_module": 'import math\nprint(math.sqrt(16))',
223
+ "import_from": 'from math import sqrt\nprint(sqrt(16))',
224
+ "decorateur": 'def mon_decorateur(f):\n def wrapper():\n print("Avant")\n f()\n print("Apres")\n return wrapper\n\n@mon_decorateur\ndef dire_bonjour():\n print("Bonjour")',
225
+ "lambda": 'carre = lambda x: x ** 2\nprint(carre(5))',
226
+ "class_def": 'class Chien:\n def __init__(self, nom):\n self.nom = nom\n def aboyer(self):\n return "Woof"\n\nmedor = Chien("Medor")\nprint(medor.aboyer())',
227
+ "context_manager_with": 'with open("test.txt", "w") as f:\n f.write("Hello")',
228
+ "break": 'for i in range(10):\n if i == 5:\n break\n print(i)',
229
+ "continue": 'for i in range(5):\n if i == 2:\n continue\n print(i)',
230
+ "operateur_comparaison": 'a, b = 5, 10\nprint(a < b)\nprint(a == b)',
231
+ "operateur_logique": 'age, pays = 20, "FR"\nif age >= 18 and pays == "FR":\n print("OK")',
232
+ "slice": 'liste = [0, 1, 2, 3, 4, 5]\nprint(liste[1:4])\nprint(liste[::-1])',
233
+ "assignation_index": 'liste = [1, 2, 3]\nliste[0] = 99\nprint(liste)',
234
+ "assignation_multiple": 'a, b, c = 1, 2, 3\nprint(a, b, c)',
235
+ "assignation_augmentee": 'compteur = 0\ncompteur += 1\nprint(compteur)',
236
+ "ternaire": 'age = 20\nstatut = "majeur" if age >= 18 else "mineur"\nprint(statut)',
237
+ "yield": 'def compter(n):\n for i in range(n):\n yield i\nfor x in compter(3):\n print(x)',
238
+ "walrus_operator": 'if (n := len([1, 2, 3])) > 0:\n print(f"Longueur: {n}")',
239
+ }
240
+ return exercices.get(concept, f'# Exercice: {concept}\n# Ecris du code qui utilise "{concept}"\npass')
@@ -0,0 +1,277 @@
1
+ """
2
+ Scanner AST : analyse tous les fichiers Python d'un projet
3
+ et extrait chaque concept avec sa position exacte (fichier:ligne).
4
+ """
5
+
6
+ import ast
7
+ import os
8
+ from pathlib import Path
9
+ from collections import defaultdict
10
+
11
+ IGNORE_DIRS = {"__pycache__", "venv", ".venv", "node_modules", ".git", ".vibetodev", ".vibetodev-vault"}
12
+ IGNORE_FILES = {"__init__.py"}
13
+
14
+
15
+ class ConceptExtractor(ast.NodeVisitor):
16
+ """Visite l'AST et extrait les concepts avec fichier:ligne."""
17
+
18
+ def __init__(self, fichier_rel, lignes_source):
19
+ self.fichier = fichier_rel
20
+ self.lignes = lignes_source
21
+ self.concepts = defaultdict(list)
22
+
23
+ def _extraire_code(self, node):
24
+ debut = max(0, getattr(node, 'lineno', 1) - 1)
25
+ fin = getattr(node, 'end_lineno', debut + 1) or (debut + 1)
26
+ if fin > len(self.lignes):
27
+ fin = len(self.lignes)
28
+ return "".join(self.lignes[debut:fin]).strip()
29
+
30
+ def _add(self, concept, node):
31
+ self.concepts[concept].append({
32
+ "fichier": self.fichier,
33
+ "ligne": getattr(node, 'lineno', 0),
34
+ "code": self._extraire_code(node),
35
+ })
36
+
37
+ def visit_Import(self, node):
38
+ for alias in node.names:
39
+ self._add("import_module", node)
40
+ self.generic_visit(node)
41
+
42
+ def visit_ImportFrom(self, node):
43
+ self._add("import_from", node)
44
+ self.generic_visit(node)
45
+
46
+ def visit_FunctionDef(self, node):
47
+ self._add("fonction_def", node)
48
+ if node.decorator_list:
49
+ self._add("decorateur", node)
50
+ for a in node.args.args:
51
+ if a.annotation:
52
+ self._add("type_hint_param", a)
53
+ if node.returns:
54
+ self._add("type_hint_return", node)
55
+ body = node.body
56
+ if body and isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant) and isinstance(body[0].value.value, str):
57
+ self._add("docstring", node)
58
+ self.generic_visit(node)
59
+
60
+ def visit_AsyncFunctionDef(self, node):
61
+ self._add("fonction_async", node)
62
+ self.generic_visit(node)
63
+
64
+ def visit_ClassDef(self, node):
65
+ self._add("class_def", node)
66
+ self.generic_visit(node)
67
+
68
+ def visit_Lambda(self, node):
69
+ self._add("lambda", node)
70
+ self.generic_visit(node)
71
+
72
+ def visit_Return(self, node):
73
+ self._add("return", node)
74
+ self.generic_visit(node)
75
+
76
+ def visit_Yield(self, node):
77
+ self._add("yield", node)
78
+ self.generic_visit(node)
79
+
80
+ def visit_Assign(self, node):
81
+ for target in node.targets:
82
+ if isinstance(target, (ast.Tuple, ast.List)):
83
+ self._add("assignation_multiple", node)
84
+ elif isinstance(target, ast.Name):
85
+ self._add("assignation", node)
86
+ elif isinstance(target, ast.Subscript):
87
+ self._add("assignation_index", node)
88
+ elif isinstance(target, ast.Attribute):
89
+ self._add("assignation_attr", node)
90
+ self.generic_visit(node)
91
+
92
+ def visit_AnnAssign(self, node):
93
+ self._add("assignation_typee", node)
94
+ self.generic_visit(node)
95
+
96
+ def visit_AugAssign(self, node):
97
+ self._add("assignation_augmentee", node)
98
+ self.generic_visit(node)
99
+
100
+ def visit_For(self, node):
101
+ self._add("boucle_for", node)
102
+ self.generic_visit(node)
103
+
104
+ def visit_AsyncFor(self, node):
105
+ self._add("boucle_async_for", node)
106
+ self.generic_visit(node)
107
+
108
+ def visit_While(self, node):
109
+ self._add("boucle_while", node)
110
+ self.generic_visit(node)
111
+
112
+ def visit_Break(self, node):
113
+ self._add("break", node)
114
+ self.generic_visit(node)
115
+
116
+ def visit_Continue(self, node):
117
+ self._add("continue", node)
118
+ self.generic_visit(node)
119
+
120
+ def visit_If(self, node):
121
+ self._add("condition_if", node)
122
+ if node.orelse:
123
+ if isinstance(node.orelse[0], ast.If):
124
+ self._add("condition_elif", node.orelse[0])
125
+ else:
126
+ self._add("condition_else", node)
127
+ self.generic_visit(node)
128
+
129
+ def visit_Try(self, node):
130
+ self._add("try_except", node)
131
+ if node.finalbody:
132
+ self._add("try_finally", node)
133
+ self.generic_visit(node)
134
+
135
+ def visit_Raise(self, node):
136
+ self._add("raise", node)
137
+ self.generic_visit(node)
138
+
139
+ def visit_With(self, node):
140
+ self._add("context_manager_with", node)
141
+ self.generic_visit(node)
142
+
143
+ def visit_AsyncWith(self, node):
144
+ self._add("context_manager_async_with", node)
145
+ self.generic_visit(node)
146
+
147
+ def visit_ListComp(self, node):
148
+ self._add("comprehension_liste", node)
149
+ self.generic_visit(node)
150
+
151
+ def visit_DictComp(self, node):
152
+ self._add("comprehension_dict", node)
153
+ self.generic_visit(node)
154
+
155
+ def visit_SetComp(self, node):
156
+ self._add("comprehension_set", node)
157
+ self.generic_visit(node)
158
+
159
+ def visit_List(self, node):
160
+ if not isinstance(node.ctx, ast.Store):
161
+ self._add("type_list", node)
162
+ self.generic_visit(node)
163
+
164
+ def visit_Dict(self, node):
165
+ self._add("type_dict", node)
166
+ self.generic_visit(node)
167
+
168
+ def visit_Set(self, node):
169
+ self._add("type_set", node)
170
+ self.generic_visit(node)
171
+
172
+ def visit_Tuple(self, node):
173
+ self._add("type_tuple", node)
174
+ self.generic_visit(node)
175
+
176
+ def visit_Constant(self, node):
177
+ val = node.value
178
+ if isinstance(val, str):
179
+ self._add("chaine" if val == "" else "type_str", node)
180
+ elif isinstance(val, bool):
181
+ self._add("type_bool", node)
182
+ elif isinstance(val, int):
183
+ self._add("type_int", node)
184
+ elif isinstance(val, float):
185
+ self._add("type_float", node)
186
+ elif val is None:
187
+ self._add("type_none", node)
188
+ elif isinstance(val, bytes):
189
+ self._add("type_bytes", node)
190
+ self.generic_visit(node)
191
+
192
+ def visit_Compare(self, node):
193
+ self._add("operateur_comparaison", node)
194
+ self.generic_visit(node)
195
+
196
+ def visit_BoolOp(self, node):
197
+ self._add("operateur_logique", node)
198
+ self.generic_visit(node)
199
+
200
+ def visit_BinOp(self, node):
201
+ op_type = type(node.op).__name__
202
+ self._add(f"binop_{op_type}", node)
203
+ self.generic_visit(node)
204
+
205
+ def visit_Call(self, node):
206
+ if isinstance(node.func, ast.Attribute):
207
+ self._add("appel_methode", node)
208
+ elif isinstance(node.func, ast.Name):
209
+ self._add("appel_fonction", node)
210
+ self.generic_visit(node)
211
+
212
+ def visit_JoinedStr(self, node):
213
+ self._add("fstring", node)
214
+ self.generic_visit(node)
215
+
216
+ def visit_Slice(self, node):
217
+ self._add("slice", node)
218
+ self.generic_visit(node)
219
+
220
+ def visit_IfExp(self, node):
221
+ self._add("ternaire", node)
222
+ self.generic_visit(node)
223
+
224
+ def visit_Starred(self, node):
225
+ self._add("unpacking", node)
226
+ self.generic_visit(node)
227
+
228
+ def visit_walrus(self, node):
229
+ """:= operateur morse (Python 3.8+)"""
230
+ self._add("walrus_operator", node)
231
+ self.generic_visit(node)
232
+
233
+
234
+ def scan_project(source_dir, recursive=True):
235
+ """
236
+ Scanne un projet et retourne {concept: [occurences]}.
237
+ Chaque occurrence a: fichier, ligne, code.
238
+ """
239
+ source = Path(source_dir)
240
+ tous = defaultdict(list)
241
+ fichiers_trouves = []
242
+
243
+ if recursive:
244
+ for root, dirs, files in os.walk(source):
245
+ dirs[:] = [d for d in dirs if d not in IGNORE_DIRS]
246
+ for f in files:
247
+ if f.endswith(".py") and f not in IGNORE_FILES:
248
+ fichiers_trouves.append(Path(root) / f)
249
+ else:
250
+ for f in source.glob("*.py"):
251
+ if f.name not in IGNORE_FILES:
252
+ fichiers_trouves.append(f)
253
+
254
+ for chemin in sorted(set(fichiers_trouves)):
255
+ try:
256
+ rel = str(chemin.relative_to(source.parent)) if source.parent in chemin.parents else str(chemin)
257
+ except ValueError:
258
+ rel = str(chemin)
259
+
260
+ try:
261
+ with open(chemin, "r", encoding="utf-8", errors="replace") as f:
262
+ lignes = f.readlines()
263
+ except Exception:
264
+ continue
265
+
266
+ try:
267
+ arbre = ast.parse("".join(lignes))
268
+ except SyntaxError:
269
+ continue
270
+
271
+ extracteur = ConceptExtractor(rel, lignes)
272
+ extracteur.visit(arbre)
273
+
274
+ for concept, occs in extracteur.concepts.items():
275
+ tous[concept].extend(occs)
276
+
277
+ return dict(tous)
@@ -0,0 +1,122 @@
1
+ """
2
+ Validateur d'exercices : execute le fichier, verifie la sortie,
3
+ et donne un feedback sans reveler la solution.
4
+ """
5
+
6
+ import ast
7
+ import sys
8
+ import io
9
+ import traceback
10
+ from pathlib import Path
11
+
12
+
13
+ def validate_exercise(fichier, concept=None):
14
+ """
15
+ Valide un fichier exercice.
16
+ Retourne un dict avec status, message, hint.
17
+ """
18
+ path = Path(fichier)
19
+ if not path.exists():
20
+ return {
21
+ "status": "error",
22
+ "message": f"Fichier '{fichier}' introuvable.",
23
+ "hint": "Cree le fichier avec les variables demandees."
24
+ }
25
+
26
+ try:
27
+ with open(path, "r", encoding="utf-8") as f:
28
+ code = f.read()
29
+ except Exception as e:
30
+ return {
31
+ "status": "error",
32
+ "message": f"Impossible de lire le fichier: {e}",
33
+ "hint": "Verifie que le fichier est accessible."
34
+ }
35
+
36
+ # Verifie la syntaxe
37
+ try:
38
+ tree = ast.parse(code)
39
+ except SyntaxError as e:
40
+ return {
41
+ "status": "error",
42
+ "message": f"Erreur de syntaxe ligne {e.lineno}: {e.msg}",
43
+ "hint": f"Verifie les parentheses, les guillemets, et l'indentation vers la ligne {e.lineno}."
44
+ }
45
+
46
+ # Execute le code
47
+ namespace = {}
48
+ old_stdout = sys.stdout
49
+ sys.stdout = io.StringIO()
50
+ try:
51
+ exec(code, namespace)
52
+ output = sys.stdout.getvalue()
53
+ except Exception as e:
54
+ sys.stdout = old_stdout
55
+ tb = traceback.format_exc()
56
+ return {
57
+ "status": "error",
58
+ "message": f"Erreur d'execution: {type(e).__name__}: {e}",
59
+ "debug": tb.split("\n")[-4:-1],
60
+ "hint": "Lis la derniere ligne de l'erreur. Que dit-elle ?"
61
+ }
62
+ finally:
63
+ sys.stdout = old_stdout
64
+
65
+ # Verifications generiques
66
+ erreurs = []
67
+ reussites = []
68
+
69
+ # Verifie qu'il y a des variables definies
70
+ user_vars = {k: v for k, v in namespace.items()
71
+ if not k.startswith("_") and not callable(v)}
72
+ if user_vars:
73
+ reussites.append(f"{len(user_vars)} variable(s) definie(s)")
74
+ else:
75
+ erreurs.append("Aucune variable definie")
76
+
77
+ # Verifie qu'il y a un print ou une sortie
78
+ if output.strip():
79
+ reussites.append(f"Sortie produite ({len(output)} caracteres)")
80
+ else:
81
+ erreurs.append("Aucune sortie (as-tu utilise print() ?)")
82
+
83
+ # Verifie les types de base si presents
84
+ for nom, val in user_vars.items():
85
+ if isinstance(val, str):
86
+ reussites.append(f" {nom}: str OK")
87
+ elif isinstance(val, int):
88
+ reussites.append(f" {nom}: int OK")
89
+ elif isinstance(val, float):
90
+ reussites.append(f" {nom}: float OK")
91
+ elif isinstance(val, bool):
92
+ reussites.append(f" {nom}: bool OK")
93
+ elif isinstance(val, list):
94
+ reussites.append(f" {nom}: list[{len(val)} elements] OK")
95
+ elif isinstance(val, dict):
96
+ reussites.append(f" {nom}: dict[{len(val)} cles] OK")
97
+ elif isinstance(val, set):
98
+ reussites.append(f" {nom}: set[{len(val)} elements] OK")
99
+ elif isinstance(val, tuple):
100
+ reussites.append(f" {nom}: tuple[{len(val)} elements] OK")
101
+
102
+ if erreurs and not reussites:
103
+ return {
104
+ "status": "error",
105
+ "message": "\n".join(erreurs),
106
+ "hint": "Ajoute des variables et un print() pour voir le resultat."
107
+ }
108
+
109
+ if erreurs:
110
+ return {
111
+ "status": "partial",
112
+ "reussites": reussites,
113
+ "erreurs": erreurs,
114
+ "hint": "Relis la lecon et verifie ce qui manque."
115
+ }
116
+
117
+ return {
118
+ "status": "success",
119
+ "reussites": reussites,
120
+ "message": "Tout est correct ! Passe au concept suivant.",
121
+ "output": output.strip()
122
+ }
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: vibetodev
3
+ Version: 1.0.0
4
+ Summary: Transforme n'importe quel projet vibecode en parcours d'apprentissage
5
+ Author: ConnectPro
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/connectpro/vibetodev
8
+ Project-URL: Source, https://github.com/connectpro/vibetodev
9
+ Keywords: learning,education,code-analysis,obsidian,ast
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Education
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
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 :: Education
21
+ Classifier: Topic :: Software Development :: Code Generators
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/markdown
24
+ Dynamic: requires-python
25
+
26
+ # VibeToDev
27
+
28
+ Transforme n'importe quel projet Python vibecodé en parcours d'apprentissage structuré.
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ pip install vibetodev
34
+ ```
35
+
36
+ Ou depuis les sources :
37
+
38
+ ```bash
39
+ git clone https://github.com/connectpro/vibetodev
40
+ cd vibetodev
41
+ pip install .
42
+ ```
43
+
44
+ ## Utilisation
45
+
46
+ ```bash
47
+ # Scanner un projet et generer un vault Obsidian
48
+ vibetodev scan /chemin/vers/mon_projet --output /chemin/vers/mon_vault
49
+
50
+ # Valider un exercice
51
+ vibetodev check mon_exercice.py
52
+
53
+ # Initialiser vibetodev dans un projet
54
+ vibetodev init /chemin/vers/mon_projet
55
+ ```
56
+
57
+ ## Comment ca marche
58
+
59
+ 1. **Scan** — Analyse AST de tous les fichiers Python, extrait 50+ concepts avec leur position exacte
60
+ 2. **Categorisation** — 9 modules pedagogiques (variables, fonctions, classes, etc.)
61
+ 3. **Generation** — Cree un vault Obsidian avec lecons, exemples de code, et exercices
62
+ 4. **Validation** — Execute et verifie les exercices sans donner la solution
63
+
64
+ ## Licence
65
+
66
+ MIT
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ vibetodev/__init__.py
5
+ vibetodev/__main__.py
6
+ vibetodev/categorizer.py
7
+ vibetodev/cli.py
8
+ vibetodev/generator.py
9
+ vibetodev/scanner.py
10
+ vibetodev/validator.py
11
+ vibetodev.egg-info/PKG-INFO
12
+ vibetodev.egg-info/SOURCES.txt
13
+ vibetodev.egg-info/dependency_links.txt
14
+ vibetodev.egg-info/entry_points.txt
15
+ vibetodev.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ vibetodev = vibetodev.cli:main
@@ -0,0 +1 @@
1
+ vibetodev