code2schema 0.1.1__tar.gz → 0.1.4__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.
Files changed (24) hide show
  1. {code2schema-0.1.1 → code2schema-0.1.4}/PKG-INFO +89 -28
  2. {code2schema-0.1.1 → code2schema-0.1.4}/README.md +88 -27
  3. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/__init__.py +1 -1
  4. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/analyzer/events.py +29 -23
  5. code2schema-0.1.4/code2schema/cli.py +207 -0
  6. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/PKG-INFO +89 -28
  7. {code2schema-0.1.1 → code2schema-0.1.4}/pyproject.toml +1 -1
  8. {code2schema-0.1.1 → code2schema-0.1.4}/tests/test_code2schema.py +47 -0
  9. code2schema-0.1.1/code2schema/cli.py +0 -105
  10. {code2schema-0.1.1 → code2schema-0.1.4}/LICENSE +0 -0
  11. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/analyzer/__init__.py +0 -0
  12. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/analyzer/cqrs.py +0 -0
  13. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/analyzer/graph.py +0 -0
  14. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/codegen/__init__.py +0 -0
  15. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/codegen/visualizer.py +0 -0
  16. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/core/__init__.py +0 -0
  17. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/core/extractor.py +0 -0
  18. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema/core/models.py +0 -0
  19. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/SOURCES.txt +0 -0
  20. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/dependency_links.txt +0 -0
  21. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/entry_points.txt +0 -0
  22. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/requires.txt +0 -0
  23. {code2schema-0.1.1 → code2schema-0.1.4}/code2schema.egg-info/top_level.txt +0 -0
  24. {code2schema-0.1.1 → code2schema-0.1.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2schema
3
- Version: 0.1.1
3
+ Version: 0.1.4
4
4
  Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
5
5
  License: Apache-2.0
6
6
  Keywords: ast,cqrs,static-analysis,code-quality,schema
@@ -34,18 +34,16 @@ Dynamic: license-file
34
34
 
35
35
  ## AI Cost Tracking
36
36
 
37
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-1.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
39
39
 
40
- - 🤖 **LLM usage:** $0.1500 (1 commits)
41
- - 👤 **Human dev:** ~$100 (1.0h @ $100/h, 30min dedup)
40
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
41
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
42
42
 
43
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
43
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
44
44
 
45
45
  ---
46
46
 
47
-
48
-
49
47
  **Semantic Compiler for Software Systems**
50
48
 
51
49
  Przekształca kod Python w model semantyczny CQRS → kontrakty API → graf architektury.
@@ -80,33 +78,81 @@ pip install "code2schema[dev]" # pytest + ruff + black
80
78
  ## Szybki start
81
79
 
82
80
  ```bash
83
- # Pełna analiza projektu
84
- code2schema ./moj_projekt \
85
- --out schema.json \
86
- --proto api.proto \
87
- --md report.md \
88
- --graphml graph.graphml \
89
- --dot graph.dot \
90
- --graph-summary \
91
- --events \
92
- --cycles
81
+ # Analiza z auto-generowanymi nazwami plików (w katalogu bieżącym)
82
+ code2schema /home/tom/github/maskservice/c2004/backend
83
+ # → c2004_schema.json
84
+
85
+ # Pełna analiza ze wszystkimi formatami wyjściowymi
86
+ code2schema ./backend --proto --md --html
87
+ # → backend_schema.json
88
+ # → backend_api.proto
89
+ # → backend_report.md
90
+ # → backend_viz.html
91
+
92
+ # Pełna analiza ze szczegółami
93
+ code2schema ./backend --proto --md --html --graph-summary --events --cycles
93
94
  ```
94
95
 
95
96
  Przykładowy output:
96
97
 
97
98
  ```
98
- ✅ Gotowe (1.2s)
99
- Modules : 87
100
- Functions: 883
101
- Queries : 612
102
- Commands : 184
103
- Orchest. : 87
104
- Workflows: 87
105
- Rules : 43
106
- Graph : 883N / 1204E
107
- schema.json
99
+ ✅ Gotowe (1.8s)
100
+ Modules : 607
101
+ Functions: 3142
102
+ Queries : 1518
103
+ Commands : 505
104
+ Orchest. : 1119
105
+ Workflows: 1118
106
+ Rules : 320
107
+ Graph : 2922N / 5612E
108
+ c2004_schema.json
109
+ → c2004_api.proto
110
+ → c2004_report.md
111
+ ```
112
+
113
+ ## Test na dużym projekcie (c2004 backend)
114
+
115
+ Analiza produkcyjnego projektu IoT (607 modułów, 3142 funkcje):
116
+
117
+ ```bash
118
+ code2schema /home/tom/github/maskservice/c2004/backend --proto --md --html
119
+ # Pliki generowane w /home/tom/github/maskservice/c2004/ (katalog projektu)
120
+ ```
121
+
122
+ **Wyniki:**
123
+ ```
124
+ ✅ Gotowe (1.8s)
125
+ Modules : 607
126
+ Functions: 3142
127
+ Queries : 1518
128
+ Commands : 505
129
+ Orchest. : 1119
130
+ Workflows: 1118
131
+ Rules : 320
132
+ Graph : 2922N / 5612E
133
+ → c2004_schema.json
134
+ → c2004_api.proto
135
+ → c2004_report.md
136
+ → c2004_viz.html
108
137
  ```
109
138
 
139
+ ## Dokumentacja i przykłady
140
+
141
+ - **[Dokumentacja](docs/)** — przykłady użycia CLI i Python API
142
+ - [CLI examples](docs/examples/cli/basic.sh)
143
+ - [Python API — basic](docs/examples/api/basic.py)
144
+ - [Python API — advanced](docs/examples/api/advanced.py)
145
+ - [Sample outputs](docs/examples/outputs/)
146
+
147
+ Wykryte cykle w architekturze CQRS (do refaktoryzacji):
148
+ - `handle → publish → _execute → publish → handle`
149
+
150
+ **Wygenerowane artefakty:**
151
+ - `c2004_schema.json` (2.9MB) — pełny model
152
+ - `c2004_api.proto` (188KB) — gRPC contracts
153
+ - `c2004_report.md` (49KB) — podsumowanie jakości
154
+ - `c2004_viz.html` (1.1MB) — **interaktywny graf D3.js** z wyszukiwaniem i filtrowaniem
155
+
110
156
  ## Użycie w kodzie
111
157
 
112
158
  ```python
@@ -138,6 +184,21 @@ print(to_proto(schema))
138
184
  print(to_markdown(schema))
139
185
  ```
140
186
 
187
+ ## Wizualizacja HTML (`--html`)
188
+
189
+ Interaktywny graf D3.js z funkcjami:
190
+ - **Kolory ról**: 🟢 Query, 🟠 Command, 🟣 Orchestrator
191
+ - **Wyszukiwanie** — filtruje węzły w czasie rzeczywistym
192
+ - **Hover** — podświetla połączenia (call graph)
193
+ - **Klik** — szczegóły funkcji (fan-out, side effects, reguły jakości)
194
+ - **Filtry** — pokazuj/ukrywaj kategorie ról
195
+ - **Force layout** — przeciąganie węzłów
196
+
197
+ ```bash
198
+ code2schema ./backend --html viz.html
199
+ # Otwórz w przeglądarce: open viz.html
200
+ ```
201
+
141
202
  ## Architektura paczki
142
203
 
143
204
  ```
@@ -3,18 +3,16 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-1.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $0.1500 (1 commits)
10
- - 👤 **Human dev:** ~$100 (1.0h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
10
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
11
11
 
12
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
12
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
-
17
-
18
16
  **Semantic Compiler for Software Systems**
19
17
 
20
18
  Przekształca kod Python w model semantyczny CQRS → kontrakty API → graf architektury.
@@ -49,33 +47,81 @@ pip install "code2schema[dev]" # pytest + ruff + black
49
47
  ## Szybki start
50
48
 
51
49
  ```bash
52
- # Pełna analiza projektu
53
- code2schema ./moj_projekt \
54
- --out schema.json \
55
- --proto api.proto \
56
- --md report.md \
57
- --graphml graph.graphml \
58
- --dot graph.dot \
59
- --graph-summary \
60
- --events \
61
- --cycles
50
+ # Analiza z auto-generowanymi nazwami plików (w katalogu bieżącym)
51
+ code2schema /home/tom/github/maskservice/c2004/backend
52
+ # → c2004_schema.json
53
+
54
+ # Pełna analiza ze wszystkimi formatami wyjściowymi
55
+ code2schema ./backend --proto --md --html
56
+ # → backend_schema.json
57
+ # → backend_api.proto
58
+ # → backend_report.md
59
+ # → backend_viz.html
60
+
61
+ # Pełna analiza ze szczegółami
62
+ code2schema ./backend --proto --md --html --graph-summary --events --cycles
62
63
  ```
63
64
 
64
65
  Przykładowy output:
65
66
 
66
67
  ```
67
- ✅ Gotowe (1.2s)
68
- Modules : 87
69
- Functions: 883
70
- Queries : 612
71
- Commands : 184
72
- Orchest. : 87
73
- Workflows: 87
74
- Rules : 43
75
- Graph : 883N / 1204E
76
- schema.json
68
+ ✅ Gotowe (1.8s)
69
+ Modules : 607
70
+ Functions: 3142
71
+ Queries : 1518
72
+ Commands : 505
73
+ Orchest. : 1119
74
+ Workflows: 1118
75
+ Rules : 320
76
+ Graph : 2922N / 5612E
77
+ c2004_schema.json
78
+ → c2004_api.proto
79
+ → c2004_report.md
80
+ ```
81
+
82
+ ## Test na dużym projekcie (c2004 backend)
83
+
84
+ Analiza produkcyjnego projektu IoT (607 modułów, 3142 funkcje):
85
+
86
+ ```bash
87
+ code2schema /home/tom/github/maskservice/c2004/backend --proto --md --html
88
+ # Pliki generowane w /home/tom/github/maskservice/c2004/ (katalog projektu)
89
+ ```
90
+
91
+ **Wyniki:**
92
+ ```
93
+ ✅ Gotowe (1.8s)
94
+ Modules : 607
95
+ Functions: 3142
96
+ Queries : 1518
97
+ Commands : 505
98
+ Orchest. : 1119
99
+ Workflows: 1118
100
+ Rules : 320
101
+ Graph : 2922N / 5612E
102
+ → c2004_schema.json
103
+ → c2004_api.proto
104
+ → c2004_report.md
105
+ → c2004_viz.html
77
106
  ```
78
107
 
108
+ ## Dokumentacja i przykłady
109
+
110
+ - **[Dokumentacja](docs/)** — przykłady użycia CLI i Python API
111
+ - [CLI examples](docs/examples/cli/basic.sh)
112
+ - [Python API — basic](docs/examples/api/basic.py)
113
+ - [Python API — advanced](docs/examples/api/advanced.py)
114
+ - [Sample outputs](docs/examples/outputs/)
115
+
116
+ Wykryte cykle w architekturze CQRS (do refaktoryzacji):
117
+ - `handle → publish → _execute → publish → handle`
118
+
119
+ **Wygenerowane artefakty:**
120
+ - `c2004_schema.json` (2.9MB) — pełny model
121
+ - `c2004_api.proto` (188KB) — gRPC contracts
122
+ - `c2004_report.md` (49KB) — podsumowanie jakości
123
+ - `c2004_viz.html` (1.1MB) — **interaktywny graf D3.js** z wyszukiwaniem i filtrowaniem
124
+
79
125
  ## Użycie w kodzie
80
126
 
81
127
  ```python
@@ -107,6 +153,21 @@ print(to_proto(schema))
107
153
  print(to_markdown(schema))
108
154
  ```
109
155
 
156
+ ## Wizualizacja HTML (`--html`)
157
+
158
+ Interaktywny graf D3.js z funkcjami:
159
+ - **Kolory ról**: 🟢 Query, 🟠 Command, 🟣 Orchestrator
160
+ - **Wyszukiwanie** — filtruje węzły w czasie rzeczywistym
161
+ - **Hover** — podświetla połączenia (call graph)
162
+ - **Klik** — szczegóły funkcji (fan-out, side effects, reguły jakości)
163
+ - **Filtry** — pokazuj/ukrywaj kategorie ról
164
+ - **Force layout** — przeciąganie węzłów
165
+
166
+ ```bash
167
+ code2schema ./backend --html viz.html
168
+ # Otwórz w przeglądarce: open viz.html
169
+ ```
170
+
110
171
  ## Architektura paczki
111
172
 
112
173
  ```
@@ -7,4 +7,4 @@ __all__ = [
7
7
  "SchemaIR", "CQRSRole", "FunctionIR", "ModuleIR",
8
8
  "extract_project", "extract_module", "analyze",
9
9
  ]
10
- __version__ = "0.1.1"
10
+ __version__ = "0.1.4"
@@ -72,39 +72,33 @@ class EventModel:
72
72
 
73
73
  # ── Inference ─────────────────────────────────────────────────────────────────
74
74
 
75
- def infer_event_model(modules: List[ModuleIR]) -> EventModel:
76
- """Przechodzi po IR i buduje model zdarzeń."""
77
- model = EventModel()
78
-
79
- all_funcs: dict[str, FunctionIR] = {}
80
- for mod in modules:
81
- for f in mod.functions:
82
- all_funcs[f.qualified_name] = f
83
- all_funcs[f.name] = f # short alias
84
-
85
- # Krok 1: znajdź emiterów
86
- emitters: dict[str, DomainEvent] = {}
75
+ def _find_emitters(modules: List[ModuleIR]) -> List[DomainEvent]:
76
+ """Krok 1: znajdź funkcje emitujące zdarzenia."""
77
+ events: List[DomainEvent] = []
87
78
  for mod in modules:
88
79
  for f in mod.functions:
89
80
  if _EVENT_EMIT_PATTERNS.search(f.name) or any(
90
81
  _EVENT_EMIT_PATTERNS.search(c) for c in f.calls
91
82
  ):
92
83
  event_name = _derive_event_name(f.name)
93
- ev = DomainEvent(name=event_name, emitted_by=f.qualified_name)
94
- emitters[f.name] = ev
95
- model.events.append(ev)
84
+ events.append(DomainEvent(name=event_name, emitted_by=f.qualified_name))
85
+ return events
96
86
 
97
- # Krok 2: znajdź handlerów
87
+
88
+ def _find_handlers(events: List[DomainEvent], modules: List[ModuleIR]) -> None:
89
+ """Krok 2: dopasuj handlery do zdarzeń (mutuje events in-place)."""
98
90
  for mod in modules:
99
91
  for f in mod.functions:
100
92
  if _EVENT_HANDLER_PATTERNS.search(f.name):
101
- # sprawdź, czy handler pasuje do jakiegoś zdarzenia
102
- for ev in model.events:
93
+ for ev in events:
103
94
  keyword = ev.name.lower().replace("event", "").strip()
104
95
  if keyword and keyword in f.name.lower():
105
96
  ev.handled_by.append(f.qualified_name)
106
97
 
107
- # Krok 3: command handlers
98
+
99
+ def _find_command_handlers(modules: List[ModuleIR]) -> List[CommandHandler]:
100
+ """Krok 3: znajdź command handlery i ich emitowane zdarzenia."""
101
+ commands: List[CommandHandler] = []
108
102
  for mod in modules:
109
103
  for f in mod.functions:
110
104
  if f.role == CQRSRole.COMMAND or _AGGREGATE_PATTERNS.search(f.name):
@@ -114,24 +108,36 @@ def infer_event_model(modules: List[ModuleIR]) -> EventModel:
114
108
  if _EVENT_EMIT_PATTERNS.search(c)
115
109
  ]
116
110
  if emits or f.role == CQRSRole.COMMAND:
117
- model.commands.append(
111
+ commands.append(
118
112
  CommandHandler(
119
113
  name=f.qualified_name,
120
114
  command=f.name,
121
115
  emits=emits,
122
116
  )
123
117
  )
118
+ return commands
119
+
124
120
 
125
- # Krok 4: agregaty moduły z operacjami CRUD
121
+ def _find_aggregates(modules: List[ModuleIR]) -> List[str]:
122
+ """Krok 4: znajdź moduły będące agregatami (CRUD score >= 2)."""
123
+ aggregates: List[str] = []
126
124
  for mod in modules:
127
125
  crud_score = sum(
128
126
  1 for f in mod.functions
129
127
  if _AGGREGATE_PATTERNS.search(f.name)
130
128
  )
131
129
  if crud_score >= 2:
132
- model.aggregates.append(mod.name)
130
+ aggregates.append(mod.name)
131
+ return aggregates
133
132
 
134
- return model
133
+
134
+ def infer_event_model(modules: List[ModuleIR]) -> EventModel:
135
+ """Przechodzi po IR i buduje model zdarzeń."""
136
+ events = _find_emitters(modules)
137
+ _find_handlers(events, modules)
138
+ commands = _find_command_handlers(modules)
139
+ aggregates = _find_aggregates(modules)
140
+ return EventModel(commands=commands, events=events, aggregates=aggregates)
135
141
 
136
142
 
137
143
  def _derive_event_name(func_name: str) -> str:
@@ -0,0 +1,207 @@
1
+ """
2
+ code2schema.cli
3
+ ~~~~~~~~~~~~~~~
4
+ CLI v3 — pełny pipeline.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import sys
10
+ import time
11
+ from pathlib import Path
12
+
13
+ from code2schema.analyzer.cqrs import analyze
14
+ from code2schema.analyzer.events import infer_event_model
15
+ from code2schema.analyzer.graph import (
16
+ build_rich_graph, detect_cycles, graph_summary, write_dot, write_graphml,
17
+ )
18
+ from code2schema.codegen import write_json, write_markdown, write_proto
19
+ from code2schema.codegen.visualizer import write_html
20
+ from code2schema.core.extractor import extract_project
21
+
22
+
23
+ _SPECIAL_PROJECT_DIRS = {"backend", "frontend", "src", "app", "api"}
24
+ _GENERIC_PATH_PARTS = {"home", "github", "workspace", "projects", "src"}
25
+
26
+
27
+ def _project_name_from_path(path: Path) -> str:
28
+ """Extract project name from path (e.g., /path/to/c2004/backend -> c2004)."""
29
+ # Try to find a meaningful name from the path
30
+ parts = path.parts
31
+ for i, part in enumerate(reversed(parts)):
32
+ if part in _SPECIAL_PROJECT_DIRS:
33
+ # Use parent directory name
34
+ if i < len(parts) - 1:
35
+ return parts[-(i + 2)]
36
+ # Skip common generic names
37
+ if part not in _GENERIC_PATH_PARTS:
38
+ return part
39
+ return path.name or "project"
40
+
41
+
42
+ def _build_parser() -> argparse.ArgumentParser:
43
+ """Build CLI argument parser."""
44
+ from code2schema import __version__
45
+ parser = argparse.ArgumentParser(prog="code2schema",
46
+ description="Semantic Compiler: Code → CQRS → Schema / Proto / Graph")
47
+ parser.add_argument("path")
48
+ parser.add_argument("-o", "--out", metavar="FILE", help="Output JSON schema (default: <project>_schema.json)")
49
+ parser.add_argument("--proto", metavar="FILE", nargs="?", const=True, default=None,
50
+ help="Output .proto file (default: <project>_api.proto if flag present)")
51
+ parser.add_argument("--md", metavar="FILE", nargs="?", const=True, default=None,
52
+ help="Output Markdown report (default: <project>_report.md if flag present)")
53
+ parser.add_argument("--graphml", metavar="FILE")
54
+ parser.add_argument("--dot", metavar="FILE")
55
+ parser.add_argument("--html", metavar="FILE", nargs="?", const=True, default=None,
56
+ help="Interactive HTML visualization (default: <project>_viz.html if flag present)")
57
+ parser.add_argument("--events", action="store_true")
58
+ parser.add_argument("--cycles", action="store_true")
59
+ parser.add_argument("--graph-summary", action="store_true")
60
+ parser.add_argument("--no-rules", action="store_true")
61
+ parser.add_argument("--exclude", nargs="*", default=[])
62
+ parser.add_argument("-q", "--quiet", action="store_true")
63
+ parser.add_argument("-V", "--version", action="version",
64
+ version=f"%(prog)s {__version__}")
65
+ return parser
66
+
67
+
68
+ def _resolve_output_dir(root: Path) -> Path:
69
+ if not root.is_dir():
70
+ return root.parent
71
+ if root.name in _SPECIAL_PROJECT_DIRS:
72
+ return root.parent
73
+ return root
74
+
75
+
76
+ def _resolve_required_path(out_dir: Path, value: str | None, default_name: str) -> Path:
77
+ return out_dir / (value or default_name)
78
+
79
+
80
+ def _resolve_optional_path(
81
+ out_dir: Path,
82
+ value: str | bool | None,
83
+ default_name: str,
84
+ ) -> Path | None:
85
+ if isinstance(value, str):
86
+ return out_dir / value
87
+ if value is True:
88
+ return out_dir / default_name
89
+ return None
90
+
91
+
92
+ def _resolve_paths(
93
+ args: argparse.Namespace,
94
+ ) -> tuple[Path, Path, Path | None, Path | None, Path | None]:
95
+ """Resolve root, project name and output file paths from CLI args."""
96
+ root = Path(args.path)
97
+ proj_name = _project_name_from_path(root)
98
+ out_dir = _resolve_output_dir(root)
99
+
100
+ out_path = _resolve_required_path(out_dir, args.out, f"{proj_name}_schema.json")
101
+ proto_path = _resolve_optional_path(out_dir, args.proto, f"{proj_name}_api.proto")
102
+ md_path = _resolve_optional_path(out_dir, args.md, f"{proj_name}_report.md")
103
+ html_path = _resolve_optional_path(out_dir, args.html, f"{proj_name}_viz.html")
104
+ return root, out_path, proto_path, md_path, html_path
105
+
106
+
107
+ def _run_extraction(args, root):
108
+ """Parse project and run CQRS analysis. Returns (modules, schema, G) or None on failure."""
109
+ modules = extract_project(root, exclude=args.exclude or None)
110
+ if not modules:
111
+ print("ERROR: Brak plików .py.", file=sys.stderr)
112
+ return None
113
+ schema = analyze(modules)
114
+ if args.no_rules:
115
+ schema.rules = []
116
+ G = build_rich_graph(schema)
117
+ return modules, schema, G
118
+
119
+
120
+ def _run_reports(args, modules, schema, G):
121
+ """Print optional reports: cycles, graph summary, events."""
122
+ if args.cycles:
123
+ cycles = detect_cycles(G)
124
+ if cycles:
125
+ print(f"\n⚠️ Cykle ({len(cycles)}):")
126
+ for c in cycles[:5]:
127
+ print(f" {' → '.join(c[:5])}")
128
+ else:
129
+ print("✅ Brak cykli.")
130
+
131
+ if args.graph_summary:
132
+ print("\n── Graph Summary ──────────────────────")
133
+ print(graph_summary(G, schema))
134
+
135
+ if args.events:
136
+ em = infer_event_model(modules)
137
+ print("\n── Event Model ────────────────────────")
138
+ print(em.summary())
139
+
140
+
141
+ def _write_outputs(args, schema, G, out_path, proto_path, md_path, html_path):
142
+ """Write all output files (JSON, proto, md, html, graphml, dot)."""
143
+ write_json(schema, out_path)
144
+ if proto_path:
145
+ write_proto(schema, proto_path)
146
+ if md_path:
147
+ write_markdown(schema, md_path)
148
+ if html_path:
149
+ write_html(schema, html_path)
150
+ if args.graphml:
151
+ write_graphml(G, Path(args.graphml))
152
+ if args.dot:
153
+ write_dot(G, Path(args.dot))
154
+
155
+
156
+ def _print_summary(modules, schema, G, t0, out_path, proto_path, md_path, html_path):
157
+ """Print final summary with metrics and output file names."""
158
+ funcs = schema.all_functions()
159
+ outputs = [f" → {out_path.name}"]
160
+ if proto_path:
161
+ outputs.append(f" → {proto_path.name}")
162
+ if md_path:
163
+ outputs.append(f" → {md_path.name}")
164
+ if html_path:
165
+ outputs.append(f" → {html_path.name}")
166
+ print(
167
+ f"\n✅ Gotowe ({time.perf_counter()-t0:.2f}s)\n"
168
+ f" Modules : {len(modules)}\n"
169
+ f" Functions: {len(funcs)}\n"
170
+ f" Queries : {len(schema.queries())}\n"
171
+ f" Commands : {len(schema.commands())}\n"
172
+ f" Orchest. : {len(schema.orchestrators())}\n"
173
+ f" Workflows: {len(schema.workflows)}\n"
174
+ f" Rules : {len(schema.rules)}\n"
175
+ f" Graph : {G.number_of_nodes()}N / {G.number_of_edges()}E\n"
176
+ + "\n".join(outputs)
177
+ )
178
+
179
+
180
+ def main(argv=None):
181
+ parser = _build_parser()
182
+ args = parser.parse_args(argv)
183
+
184
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
185
+ if not root.exists():
186
+ print(f"ERROR: {root}", file=sys.stderr)
187
+ return 1
188
+
189
+ t0 = time.perf_counter()
190
+ if not args.quiet:
191
+ print(f"⏳ Parsing: {root}")
192
+
193
+ result = _run_extraction(args, root)
194
+ if result is None:
195
+ return 1
196
+ modules, schema, G = result
197
+
198
+ _run_reports(args, modules, schema, G)
199
+ _write_outputs(args, schema, G, out_path, proto_path, md_path, html_path)
200
+
201
+ if not args.quiet:
202
+ _print_summary(modules, schema, G, t0, out_path, proto_path, md_path, html_path)
203
+ return 0
204
+
205
+
206
+ if __name__ == "__main__":
207
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code2schema
3
- Version: 0.1.1
3
+ Version: 0.1.4
4
4
  Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
5
5
  License: Apache-2.0
6
6
  Keywords: ast,cqrs,static-analysis,code-quality,schema
@@ -34,18 +34,16 @@ Dynamic: license-file
34
34
 
35
35
  ## AI Cost Tracking
36
36
 
37
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.1-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.15-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-1.0h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.1.4-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
38
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$0.45-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-3.5h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
39
39
 
40
- - 🤖 **LLM usage:** $0.1500 (1 commits)
41
- - 👤 **Human dev:** ~$100 (1.0h @ $100/h, 30min dedup)
40
+ - 🤖 **LLM usage:** $0.4500 (3 commits)
41
+ - 👤 **Human dev:** ~$350 (3.5h @ $100/h, 30min dedup)
42
42
 
43
- Generated on 2026-05-04 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
43
+ Generated on 2026-05-07 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
44
44
 
45
45
  ---
46
46
 
47
-
48
-
49
47
  **Semantic Compiler for Software Systems**
50
48
 
51
49
  Przekształca kod Python w model semantyczny CQRS → kontrakty API → graf architektury.
@@ -80,33 +78,81 @@ pip install "code2schema[dev]" # pytest + ruff + black
80
78
  ## Szybki start
81
79
 
82
80
  ```bash
83
- # Pełna analiza projektu
84
- code2schema ./moj_projekt \
85
- --out schema.json \
86
- --proto api.proto \
87
- --md report.md \
88
- --graphml graph.graphml \
89
- --dot graph.dot \
90
- --graph-summary \
91
- --events \
92
- --cycles
81
+ # Analiza z auto-generowanymi nazwami plików (w katalogu bieżącym)
82
+ code2schema /home/tom/github/maskservice/c2004/backend
83
+ # → c2004_schema.json
84
+
85
+ # Pełna analiza ze wszystkimi formatami wyjściowymi
86
+ code2schema ./backend --proto --md --html
87
+ # → backend_schema.json
88
+ # → backend_api.proto
89
+ # → backend_report.md
90
+ # → backend_viz.html
91
+
92
+ # Pełna analiza ze szczegółami
93
+ code2schema ./backend --proto --md --html --graph-summary --events --cycles
93
94
  ```
94
95
 
95
96
  Przykładowy output:
96
97
 
97
98
  ```
98
- ✅ Gotowe (1.2s)
99
- Modules : 87
100
- Functions: 883
101
- Queries : 612
102
- Commands : 184
103
- Orchest. : 87
104
- Workflows: 87
105
- Rules : 43
106
- Graph : 883N / 1204E
107
- schema.json
99
+ ✅ Gotowe (1.8s)
100
+ Modules : 607
101
+ Functions: 3142
102
+ Queries : 1518
103
+ Commands : 505
104
+ Orchest. : 1119
105
+ Workflows: 1118
106
+ Rules : 320
107
+ Graph : 2922N / 5612E
108
+ c2004_schema.json
109
+ → c2004_api.proto
110
+ → c2004_report.md
111
+ ```
112
+
113
+ ## Test na dużym projekcie (c2004 backend)
114
+
115
+ Analiza produkcyjnego projektu IoT (607 modułów, 3142 funkcje):
116
+
117
+ ```bash
118
+ code2schema /home/tom/github/maskservice/c2004/backend --proto --md --html
119
+ # Pliki generowane w /home/tom/github/maskservice/c2004/ (katalog projektu)
120
+ ```
121
+
122
+ **Wyniki:**
123
+ ```
124
+ ✅ Gotowe (1.8s)
125
+ Modules : 607
126
+ Functions: 3142
127
+ Queries : 1518
128
+ Commands : 505
129
+ Orchest. : 1119
130
+ Workflows: 1118
131
+ Rules : 320
132
+ Graph : 2922N / 5612E
133
+ → c2004_schema.json
134
+ → c2004_api.proto
135
+ → c2004_report.md
136
+ → c2004_viz.html
108
137
  ```
109
138
 
139
+ ## Dokumentacja i przykłady
140
+
141
+ - **[Dokumentacja](docs/)** — przykłady użycia CLI i Python API
142
+ - [CLI examples](docs/examples/cli/basic.sh)
143
+ - [Python API — basic](docs/examples/api/basic.py)
144
+ - [Python API — advanced](docs/examples/api/advanced.py)
145
+ - [Sample outputs](docs/examples/outputs/)
146
+
147
+ Wykryte cykle w architekturze CQRS (do refaktoryzacji):
148
+ - `handle → publish → _execute → publish → handle`
149
+
150
+ **Wygenerowane artefakty:**
151
+ - `c2004_schema.json` (2.9MB) — pełny model
152
+ - `c2004_api.proto` (188KB) — gRPC contracts
153
+ - `c2004_report.md` (49KB) — podsumowanie jakości
154
+ - `c2004_viz.html` (1.1MB) — **interaktywny graf D3.js** z wyszukiwaniem i filtrowaniem
155
+
110
156
  ## Użycie w kodzie
111
157
 
112
158
  ```python
@@ -138,6 +184,21 @@ print(to_proto(schema))
138
184
  print(to_markdown(schema))
139
185
  ```
140
186
 
187
+ ## Wizualizacja HTML (`--html`)
188
+
189
+ Interaktywny graf D3.js z funkcjami:
190
+ - **Kolory ról**: 🟢 Query, 🟠 Command, 🟣 Orchestrator
191
+ - **Wyszukiwanie** — filtruje węzły w czasie rzeczywistym
192
+ - **Hover** — podświetla połączenia (call graph)
193
+ - **Klik** — szczegóły funkcji (fan-out, side effects, reguły jakości)
194
+ - **Filtry** — pokazuj/ukrywaj kategorie ról
195
+ - **Force layout** — przeciąganie węzłów
196
+
197
+ ```bash
198
+ code2schema ./backend --html viz.html
199
+ # Otwórz w przeglądarce: open viz.html
200
+ ```
201
+
141
202
  ## Architektura paczki
142
203
 
143
204
  ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code2schema"
7
- version = "0.1.1"
7
+ version = "0.1.4"
8
8
  description = "Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1,6 +1,7 @@
1
1
  """Testy jednostkowe code2schema."""
2
2
  from __future__ import annotations
3
3
 
4
+ import argparse
4
5
  import ast
5
6
  import textwrap
6
7
  from pathlib import Path
@@ -10,6 +11,7 @@ import pytest
10
11
  from code2schema.core.models import CQRSRole, SideEffect
11
12
  from code2schema.core.extractor import extract_module
12
13
  from code2schema.analyzer.cqrs import _infer_role, analyze, build_call_graph
14
+ from code2schema.cli import _resolve_paths
13
15
  from code2schema.codegen import to_json, to_proto, to_markdown
14
16
 
15
17
 
@@ -216,3 +218,48 @@ def test_event_model_summary(sample_module):
216
218
  summary = em.summary()
217
219
  assert "Commands" in summary
218
220
  assert "Domain Events" in summary
221
+
222
+
223
+ # ── CLI Paths ─────────────────────────────────────────────────────────────────
224
+
225
+ def test_resolve_paths_for_special_project_dir(tmp_path: Path):
226
+ backend_dir = tmp_path / "c2004" / "backend"
227
+ backend_dir.mkdir(parents=True)
228
+
229
+ args = argparse.Namespace(
230
+ path=str(backend_dir),
231
+ out=None,
232
+ proto=True,
233
+ md=True,
234
+ html=True,
235
+ )
236
+
237
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
238
+
239
+ assert root == backend_dir
240
+ assert out_path == backend_dir.parent / "c2004_schema.json"
241
+ assert proto_path == backend_dir.parent / "c2004_api.proto"
242
+ assert md_path == backend_dir.parent / "c2004_report.md"
243
+ assert html_path == backend_dir.parent / "c2004_viz.html"
244
+
245
+
246
+ def test_resolve_paths_for_file_input_with_custom_outputs(tmp_path: Path):
247
+ file_path = tmp_path / "repo" / "main.py"
248
+ file_path.parent.mkdir(parents=True)
249
+ file_path.write_text("def main():\n pass\n")
250
+
251
+ args = argparse.Namespace(
252
+ path=str(file_path),
253
+ out="schema.json",
254
+ proto="api.proto",
255
+ md=None,
256
+ html=None,
257
+ )
258
+
259
+ root, out_path, proto_path, md_path, html_path = _resolve_paths(args)
260
+
261
+ assert root == file_path
262
+ assert out_path == file_path.parent / "schema.json"
263
+ assert proto_path == file_path.parent / "api.proto"
264
+ assert md_path is None
265
+ assert html_path is None
@@ -1,105 +0,0 @@
1
- """
2
- code2schema.cli
3
- ~~~~~~~~~~~~~~~
4
- CLI v3 — pełny pipeline.
5
- """
6
- from __future__ import annotations
7
-
8
- import argparse
9
- import sys
10
- import time
11
- from pathlib import Path
12
-
13
- from code2schema.analyzer.cqrs import analyze
14
- from code2schema.analyzer.events import infer_event_model
15
- from code2schema.analyzer.graph import (
16
- build_rich_graph, detect_cycles, graph_summary, write_dot, write_graphml,
17
- )
18
- from code2schema.codegen import write_json, write_markdown, write_proto
19
- from code2schema.core.extractor import extract_project
20
-
21
-
22
- def main(argv=None):
23
- parser = argparse.ArgumentParser(prog="code2schema",
24
- description="Semantic Compiler: Code → CQRS → Schema / Proto / Graph")
25
- parser.add_argument("path")
26
- parser.add_argument("-o", "--out", default="schema.json")
27
- parser.add_argument("--proto", metavar="FILE")
28
- parser.add_argument("--md", metavar="FILE")
29
- parser.add_argument("--graphml", metavar="FILE")
30
- parser.add_argument("--dot", metavar="FILE")
31
- parser.add_argument("--events", action="store_true")
32
- parser.add_argument("--cycles", action="store_true")
33
- parser.add_argument("--graph-summary", action="store_true")
34
- parser.add_argument("--no-rules", action="store_true")
35
- parser.add_argument("--exclude", nargs="*", default=[])
36
- parser.add_argument("-q", "--quiet", action="store_true")
37
- args = parser.parse_args(argv)
38
-
39
- root = Path(args.path)
40
- if not root.exists():
41
- print(f"ERROR: {root}", file=sys.stderr)
42
- return 1
43
-
44
- t0 = time.perf_counter()
45
- if not args.quiet:
46
- print(f"⏳ Parsing: {root}")
47
-
48
- modules = extract_project(root, exclude=args.exclude or None)
49
- if not modules:
50
- print("ERROR: Brak plików .py.", file=sys.stderr)
51
- return 1
52
-
53
- schema = analyze(modules)
54
- if args.no_rules:
55
- schema.rules = []
56
-
57
- G = build_rich_graph(schema)
58
-
59
- if args.cycles:
60
- cycles = detect_cycles(G)
61
- if cycles:
62
- print(f"\n⚠️ Cykle ({len(cycles)}):")
63
- for c in cycles[:5]:
64
- print(f" {' → '.join(c[:5])}")
65
- else:
66
- print("✅ Brak cykli.")
67
-
68
- if args.graph_summary:
69
- print("\n── Graph Summary ──────────────────────")
70
- print(graph_summary(G, schema))
71
-
72
- if args.events:
73
- em = infer_event_model(modules)
74
- print("\n── Event Model ────────────────────────")
75
- print(em.summary())
76
-
77
- write_json(schema, Path(args.out))
78
- if args.proto:
79
- write_proto(schema, Path(args.proto))
80
- if args.md:
81
- write_markdown(schema, Path(args.md))
82
- if args.graphml:
83
- write_graphml(G, Path(args.graphml))
84
- if args.dot:
85
- write_dot(G, Path(args.dot))
86
-
87
- if not args.quiet:
88
- funcs = schema.all_functions()
89
- print(
90
- f"\n✅ Gotowe ({time.perf_counter()-t0:.2f}s)\n"
91
- f" Modules : {len(modules)}\n"
92
- f" Functions: {len(funcs)}\n"
93
- f" Queries : {len(schema.queries())}\n"
94
- f" Commands : {len(schema.commands())}\n"
95
- f" Orchest. : {len(schema.orchestrators())}\n"
96
- f" Workflows: {len(schema.workflows)}\n"
97
- f" Rules : {len(schema.rules)}\n"
98
- f" Graph : {G.number_of_nodes()}N / {G.number_of_edges()}E\n"
99
- f" → {args.out}"
100
- )
101
- return 0
102
-
103
-
104
- if __name__ == "__main__":
105
- raise SystemExit(main())
File without changes
File without changes