code2schema 0.1.8__tar.gz → 0.1.9__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.
- {code2schema-0.1.8 → code2schema-0.1.9}/PKG-INFO +11 -7
- {code2schema-0.1.8 → code2schema-0.1.9}/README.md +8 -3
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/__init__.py +1 -1
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/analyzer/events.py +5 -3
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/analyzer/graph.py +11 -2
- code2schema-0.1.9/code2schema/mcp_server.py +305 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/PKG-INFO +11 -7
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/SOURCES.txt +3 -1
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/requires.txt +2 -1
- {code2schema-0.1.8 → code2schema-0.1.9}/pyproject.toml +14 -7
- code2schema-0.1.9/tests/test_event_handlers.py +30 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/LICENSE +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/analyzer/__init__.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/analyzer/cqrs.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/cli.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/codegen/__init__.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/codegen/visualizer.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/core/__init__.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/core/extractor.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema/core/models.py +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/dependency_links.txt +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/entry_points.txt +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/code2schema.egg-info/top_level.txt +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/setup.cfg +0 -0
- {code2schema-0.1.8 → code2schema-0.1.9}/tests/test_code2schema.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2schema
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
|
|
5
|
-
License: Apache-2.0
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
6
|
Keywords: ast,cqrs,static-analysis,code-quality,schema
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -24,9 +24,8 @@ Requires-Dist: pytest>=8; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
25
|
Requires-Dist: black; extra == "dev"
|
|
26
26
|
Requires-Dist: ruff; extra == "dev"
|
|
27
|
-
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
28
27
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
29
|
-
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
28
|
+
Requires-Dist: pfix>=0.1.60; python_version >= "3.10" and extra == "dev"
|
|
30
29
|
Dynamic: license-file
|
|
31
30
|
|
|
32
31
|
# code2schema
|
|
@@ -34,11 +33,13 @@ Dynamic: license-file
|
|
|
34
33
|
|
|
35
34
|
## AI Cost Tracking
|
|
36
35
|
|
|
37
|
-
   
|
|
37
|
+
  
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
- 🤖 **LLM usage:** $6.7712 (15 commits)
|
|
40
|
+
- 👤 **Human dev:** ~$1736 (17.4h @ $100/h, 30min dedup)
|
|
40
41
|
|
|
41
|
-
Generated on 2026-
|
|
42
|
+
Generated on 2026-09-13 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
@@ -268,3 +269,6 @@ MIT
|
|
|
268
269
|
## License
|
|
269
270
|
|
|
270
271
|
Licensed under Apache-2.0.
|
|
272
|
+
## Dependency maintenance
|
|
273
|
+
|
|
274
|
+
See [dependency updates and Python tool groups](docs/dependencies.md) for locked tests, daily updates and freshness checks.
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
|
|
4
4
|
## AI Cost Tracking
|
|
5
5
|
|
|
6
|
-
   
|
|
7
|
+
  
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
- 🤖 **LLM usage:** $6.7712 (15 commits)
|
|
10
|
+
- 👤 **Human dev:** ~$1736 (17.4h @ $100/h, 30min dedup)
|
|
9
11
|
|
|
10
|
-
Generated on 2026-
|
|
12
|
+
Generated on 2026-09-13 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -237,3 +239,6 @@ MIT
|
|
|
237
239
|
## License
|
|
238
240
|
|
|
239
241
|
Licensed under Apache-2.0.
|
|
242
|
+
## Dependency maintenance
|
|
243
|
+
|
|
244
|
+
See [dependency updates and Python tool groups](docs/dependencies.md) for locked tests, daily updates and freshness checks.
|
|
@@ -89,12 +89,14 @@ def _find_emitters(modules: List[ModuleIR]) -> List[DomainEvent]:
|
|
|
89
89
|
|
|
90
90
|
def _find_handlers(events: List[DomainEvent], modules: List[ModuleIR]) -> None:
|
|
91
91
|
"""Krok 2: dopasuj handlery do zdarzeń (mutuje events in-place)."""
|
|
92
|
+
# Event keywords are invariant for this pass; normalize each only once.
|
|
93
|
+
keywords = [(ev, ev.name.lower().replace("event", "").strip()) for ev in events]
|
|
92
94
|
for mod in modules:
|
|
93
95
|
for f in mod.functions:
|
|
94
96
|
if _EVENT_HANDLER_PATTERNS.search(f.name):
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if keyword and keyword in
|
|
97
|
+
handler_name = f.name.lower()
|
|
98
|
+
for ev, keyword in keywords:
|
|
99
|
+
if keyword and keyword in handler_name:
|
|
98
100
|
ev.handled_by.append(f.qualified_name)
|
|
99
101
|
|
|
100
102
|
|
|
@@ -54,10 +54,19 @@ def build_rich_graph(schema: SchemaIR) -> nx.DiGraph:
|
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def centrality_report(G: nx.DiGraph, top_n: int = 10) -> List[Tuple[str, float]]:
|
|
57
|
-
"""PageRank — funkcje najważniejsze architektonicznie.
|
|
57
|
+
"""PageRank — funkcje najważniejsze architektonicznie.
|
|
58
|
+
|
|
59
|
+
Falls back to in-degree ranking when scipy is unavailable
|
|
60
|
+
(networkx pagerank delegates to scipy on some versions).
|
|
61
|
+
"""
|
|
58
62
|
if G.number_of_edges() == 0:
|
|
59
63
|
return []
|
|
60
|
-
|
|
64
|
+
try:
|
|
65
|
+
pr = nx.pagerank(G, alpha=0.85)
|
|
66
|
+
except ModuleNotFoundError:
|
|
67
|
+
pr = dict(G.in_degree())
|
|
68
|
+
total = sum(pr.values()) or 1
|
|
69
|
+
pr = {node: deg / total for node, deg in pr.items()}
|
|
61
70
|
return sorted(pr.items(), key=lambda x: x[1], reverse=True)[:top_n]
|
|
62
71
|
|
|
63
72
|
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""code2schema MCP server — dependency-free stdio JSON-RPC.
|
|
2
|
+
|
|
3
|
+
Adopts wellmanifest/poa (typed tools, closed inputSchema, fail-closed
|
|
4
|
+
dispatch) and wellmanifest/logs (append-only hash-chained JSONL event
|
|
5
|
+
stream under $XDG_STATE_HOME/code2schema/mcp-events.jsonl, overridable
|
|
6
|
+
with CODE2SCHEMA_MCP_EVENT_LOG).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import sys
|
|
15
|
+
import time
|
|
16
|
+
import uuid
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Callable
|
|
19
|
+
|
|
20
|
+
_PROTOCOL_VERSION = "2024-11-05"
|
|
21
|
+
_NOTIFICATIONS = frozenset({"notifications/initialized", "notifications/cancelled"})
|
|
22
|
+
|
|
23
|
+
SERVER_NAME = "code2schema"
|
|
24
|
+
try:
|
|
25
|
+
from importlib.metadata import version as _pkg_version
|
|
26
|
+
|
|
27
|
+
SERVER_VERSION = _pkg_version("code2schema")
|
|
28
|
+
except Exception:
|
|
29
|
+
SERVER_VERSION = "0.0.0"
|
|
30
|
+
|
|
31
|
+
_ZERO_HASH = "0" * 64
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _event_log_path() -> Path:
|
|
35
|
+
override = os.environ.get("CODE2SCHEMA_MCP_EVENT_LOG")
|
|
36
|
+
if override:
|
|
37
|
+
return Path(override)
|
|
38
|
+
state_home = Path(os.environ.get("XDG_STATE_HOME", Path.home() / ".local" / "state"))
|
|
39
|
+
return state_home / "code2schema" / "mcp-events.jsonl"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _emit_event(tool: str, status: str, duration_ms: int, detail: str = "") -> None:
|
|
43
|
+
"""Append one hash-chained event; logging failure never breaks a tool call."""
|
|
44
|
+
try:
|
|
45
|
+
path = _event_log_path()
|
|
46
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
47
|
+
prev = _ZERO_HASH
|
|
48
|
+
if path.exists() and path.stat().st_size:
|
|
49
|
+
with path.open("rb") as fh:
|
|
50
|
+
fh.seek(0, os.SEEK_END)
|
|
51
|
+
tail = fh.read(65536).decode("utf-8", errors="replace")
|
|
52
|
+
last = tail.rstrip().rsplit("\n", 1)[-1]
|
|
53
|
+
prev = json.loads(last).get("event_hash", _ZERO_HASH)
|
|
54
|
+
event = {
|
|
55
|
+
"schema": "code2schema.mcp/event/v1",
|
|
56
|
+
"event_id": f"event:{uuid.uuid4().hex[:24]}",
|
|
57
|
+
"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
|
58
|
+
"actor": "agent:mcp",
|
|
59
|
+
"tool": tool,
|
|
60
|
+
"status": status,
|
|
61
|
+
"duration_ms": duration_ms,
|
|
62
|
+
"detail": detail[:200],
|
|
63
|
+
"prev_hash": prev,
|
|
64
|
+
}
|
|
65
|
+
body = json.dumps(event, sort_keys=True)
|
|
66
|
+
event["event_hash"] = hashlib.sha256(body.encode()).hexdigest()
|
|
67
|
+
with path.open("a", encoding="utf-8") as fh:
|
|
68
|
+
fh.write(json.dumps(event) + "\n")
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _load_graph(path: str, exclude: list[str] | None):
|
|
74
|
+
from code2schema.analyzer.cqrs import analyze
|
|
75
|
+
from code2schema.analyzer.graph import build_rich_graph
|
|
76
|
+
from code2schema.core.extractor import extract_project
|
|
77
|
+
|
|
78
|
+
root = Path(path)
|
|
79
|
+
if not root.exists():
|
|
80
|
+
raise FileNotFoundError(f"Path does not exist: {path}")
|
|
81
|
+
modules = extract_project(root, exclude=exclude or None)
|
|
82
|
+
if not modules:
|
|
83
|
+
raise ValueError(f"No .py files found under: {path}")
|
|
84
|
+
schema = analyze(modules)
|
|
85
|
+
return modules, schema, build_rich_graph(schema)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _tool_analyze(args: dict[str, Any]) -> str:
|
|
89
|
+
from code2schema.codegen import to_json
|
|
90
|
+
|
|
91
|
+
_, schema, _ = _load_graph(args["path"], args.get("exclude"))
|
|
92
|
+
return to_json(schema)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _tool_summary(args: dict[str, Any]) -> str:
|
|
96
|
+
from code2schema.analyzer.events import infer_event_model
|
|
97
|
+
from code2schema.analyzer.graph import detect_cycles, graph_summary
|
|
98
|
+
|
|
99
|
+
modules, schema, G = _load_graph(args["path"], args.get("exclude"))
|
|
100
|
+
cycles = detect_cycles(G)
|
|
101
|
+
result = {
|
|
102
|
+
"modules": len(modules),
|
|
103
|
+
"functions": len(schema.all_functions()),
|
|
104
|
+
"queries": len(schema.queries()),
|
|
105
|
+
"commands": len(schema.commands()),
|
|
106
|
+
"orchestrators": len(schema.orchestrators()),
|
|
107
|
+
"workflows": len(schema.workflows),
|
|
108
|
+
"rules": len(schema.rules),
|
|
109
|
+
"graph_nodes": G.number_of_nodes(),
|
|
110
|
+
"graph_edges": G.number_of_edges(),
|
|
111
|
+
"cycles": len(cycles),
|
|
112
|
+
"graph_summary": graph_summary(G, schema),
|
|
113
|
+
"event_model": infer_event_model(modules).summary(),
|
|
114
|
+
}
|
|
115
|
+
return json.dumps(result, indent=2, default=str)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _tool_cycles(args: dict[str, Any]) -> str:
|
|
119
|
+
from code2schema.analyzer.graph import detect_cycles
|
|
120
|
+
|
|
121
|
+
_, _, G = _load_graph(args["path"], args.get("exclude"))
|
|
122
|
+
return json.dumps({"cycles": detect_cycles(G)}, indent=2)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _tool_proto(args: dict[str, Any]) -> str:
|
|
126
|
+
from code2schema.codegen import to_proto
|
|
127
|
+
|
|
128
|
+
_, schema, _ = _load_graph(args["path"], args.get("exclude"))
|
|
129
|
+
return to_proto(schema)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
_PATH_PROPERTIES = {
|
|
133
|
+
"path": {"type": "string", "description": "Project directory to analyze"},
|
|
134
|
+
"exclude": {
|
|
135
|
+
"type": "array",
|
|
136
|
+
"items": {"type": "string"},
|
|
137
|
+
"description": "Directory/file names to exclude",
|
|
138
|
+
},
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
TOOL_SCHEMAS: dict[str, dict[str, Any]] = {
|
|
142
|
+
"code2schema_analyze": {
|
|
143
|
+
"name": "code2schema_analyze",
|
|
144
|
+
"description": "Compile a Python project to the full CQRS SchemaIR JSON (commands, queries, workflows, rules).",
|
|
145
|
+
"inputSchema": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": _PATH_PROPERTIES,
|
|
148
|
+
"required": ["path"],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
"code2schema_summary": {
|
|
152
|
+
"name": "code2schema_summary",
|
|
153
|
+
"description": "Compact analysis summary: module/function/command counts, graph size, cycles, event model.",
|
|
154
|
+
"inputSchema": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": _PATH_PROPERTIES,
|
|
157
|
+
"required": ["path"],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
"code2schema_cycles": {
|
|
161
|
+
"name": "code2schema_cycles",
|
|
162
|
+
"description": "Detect dependency cycles in the CQRS graph.",
|
|
163
|
+
"inputSchema": {
|
|
164
|
+
"type": "object",
|
|
165
|
+
"properties": _PATH_PROPERTIES,
|
|
166
|
+
"required": ["path"],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
"code2schema_proto": {
|
|
170
|
+
"name": "code2schema_proto",
|
|
171
|
+
"description": "Generate proto3 definition from the CQRS schema.",
|
|
172
|
+
"inputSchema": {
|
|
173
|
+
"type": "object",
|
|
174
|
+
"properties": _PATH_PROPERTIES,
|
|
175
|
+
"required": ["path"],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
TOOL_HANDLERS: dict[str, Callable[[dict[str, Any]], str]] = {
|
|
181
|
+
"code2schema_analyze": _tool_analyze,
|
|
182
|
+
"code2schema_summary": _tool_summary,
|
|
183
|
+
"code2schema_cycles": _tool_cycles,
|
|
184
|
+
"code2schema_proto": _tool_proto,
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _handle_initialize(request_id: Any, params: dict[str, Any] | None) -> dict[str, Any]:
|
|
189
|
+
client_version = (params or {}).get("protocolVersion", _PROTOCOL_VERSION)
|
|
190
|
+
return {
|
|
191
|
+
"jsonrpc": "2.0",
|
|
192
|
+
"id": request_id,
|
|
193
|
+
"result": {
|
|
194
|
+
"protocolVersion": client_version,
|
|
195
|
+
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
|
|
196
|
+
"capabilities": {"tools": {}},
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _handle_tools_list(request_id: Any) -> dict[str, Any]:
|
|
202
|
+
tools = [
|
|
203
|
+
{
|
|
204
|
+
"name": schema["name"],
|
|
205
|
+
"description": schema["description"],
|
|
206
|
+
"inputSchema": schema["inputSchema"],
|
|
207
|
+
}
|
|
208
|
+
for schema in TOOL_SCHEMAS.values()
|
|
209
|
+
]
|
|
210
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": {"tools": tools}}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _handle_tools_call(request_id: Any, params: dict[str, Any]) -> dict[str, Any]:
|
|
214
|
+
tool_name = params.get("name")
|
|
215
|
+
arguments = params.get("arguments", {}) or {}
|
|
216
|
+
|
|
217
|
+
if tool_name not in TOOL_HANDLERS:
|
|
218
|
+
return {
|
|
219
|
+
"jsonrpc": "2.0",
|
|
220
|
+
"id": request_id,
|
|
221
|
+
"error": {"code": -32601, "message": f"Tool '{tool_name}' not found"},
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
start = time.monotonic()
|
|
225
|
+
try:
|
|
226
|
+
result = TOOL_HANDLERS[tool_name](arguments)
|
|
227
|
+
_emit_event(tool_name, "ok", int((time.monotonic() - start) * 1000))
|
|
228
|
+
return {
|
|
229
|
+
"jsonrpc": "2.0",
|
|
230
|
+
"id": request_id,
|
|
231
|
+
"result": {"content": [{"type": "text", "text": result}]},
|
|
232
|
+
}
|
|
233
|
+
except Exception as exc:
|
|
234
|
+
_emit_event(
|
|
235
|
+
tool_name, "error", int((time.monotonic() - start) * 1000), str(exc)
|
|
236
|
+
)
|
|
237
|
+
return {
|
|
238
|
+
"jsonrpc": "2.0",
|
|
239
|
+
"id": request_id,
|
|
240
|
+
"error": {"code": -32603, "message": f"Tool execution failed: {exc}"},
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def handle_request(request: dict[str, Any]) -> dict[str, Any]:
|
|
245
|
+
method = request.get("method", "")
|
|
246
|
+
params = request.get("params", {}) or {}
|
|
247
|
+
request_id = request.get("id")
|
|
248
|
+
|
|
249
|
+
if method in _NOTIFICATIONS:
|
|
250
|
+
return {}
|
|
251
|
+
if method == "initialize":
|
|
252
|
+
return _handle_initialize(request_id, params)
|
|
253
|
+
if method == "tools/list":
|
|
254
|
+
return _handle_tools_list(request_id)
|
|
255
|
+
if method == "tools/call":
|
|
256
|
+
return _handle_tools_call(request_id, params)
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
"jsonrpc": "2.0",
|
|
260
|
+
"id": request_id,
|
|
261
|
+
"error": {"code": -32601, "message": f"Method '{method}' not found"},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def run_server() -> None:
|
|
266
|
+
print("code2schema MCP Server started", file=sys.stderr)
|
|
267
|
+
print(f"Available tools: {', '.join(sorted(TOOL_SCHEMAS))}", file=sys.stderr)
|
|
268
|
+
|
|
269
|
+
for line in sys.stdin:
|
|
270
|
+
line = line.strip()
|
|
271
|
+
if not line:
|
|
272
|
+
continue
|
|
273
|
+
try:
|
|
274
|
+
request = json.loads(line)
|
|
275
|
+
response = handle_request(request)
|
|
276
|
+
if response:
|
|
277
|
+
print(json.dumps(response), flush=True)
|
|
278
|
+
except json.JSONDecodeError as exc:
|
|
279
|
+
print(
|
|
280
|
+
json.dumps(
|
|
281
|
+
{
|
|
282
|
+
"jsonrpc": "2.0",
|
|
283
|
+
"error": {"code": -32700, "message": f"Parse error: {exc}"},
|
|
284
|
+
}
|
|
285
|
+
),
|
|
286
|
+
flush=True,
|
|
287
|
+
)
|
|
288
|
+
except Exception as exc:
|
|
289
|
+
print(
|
|
290
|
+
json.dumps(
|
|
291
|
+
{
|
|
292
|
+
"jsonrpc": "2.0",
|
|
293
|
+
"error": {"code": -32603, "message": f"Internal error: {exc}"},
|
|
294
|
+
}
|
|
295
|
+
),
|
|
296
|
+
flush=True,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def main() -> None:
|
|
301
|
+
run_server()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
if __name__ == "__main__":
|
|
305
|
+
main()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code2schema
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema
|
|
5
|
-
License: Apache-2.0
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
6
|
Keywords: ast,cqrs,static-analysis,code-quality,schema
|
|
7
7
|
Classifier: Development Status :: 3 - Alpha
|
|
8
8
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -24,9 +24,8 @@ Requires-Dist: pytest>=8; extra == "dev"
|
|
|
24
24
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
25
25
|
Requires-Dist: black; extra == "dev"
|
|
26
26
|
Requires-Dist: ruff; extra == "dev"
|
|
27
|
-
Requires-Dist: goal>=2.1.0; extra == "dev"
|
|
28
27
|
Requires-Dist: costs>=0.1.20; extra == "dev"
|
|
29
|
-
Requires-Dist: pfix>=0.1.60; extra == "dev"
|
|
28
|
+
Requires-Dist: pfix>=0.1.60; python_version >= "3.10" and extra == "dev"
|
|
30
29
|
Dynamic: license-file
|
|
31
30
|
|
|
32
31
|
# code2schema
|
|
@@ -34,11 +33,13 @@ Dynamic: license-file
|
|
|
34
33
|
|
|
35
34
|
## AI Cost Tracking
|
|
36
35
|
|
|
37
|
-
   
|
|
37
|
+
  
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
- 🤖 **LLM usage:** $6.7712 (15 commits)
|
|
40
|
+
- 👤 **Human dev:** ~$1736 (17.4h @ $100/h, 30min dedup)
|
|
40
41
|
|
|
41
|
-
Generated on 2026-
|
|
42
|
+
Generated on 2026-09-13 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
|
|
42
43
|
|
|
43
44
|
---
|
|
44
45
|
|
|
@@ -268,3 +269,6 @@ MIT
|
|
|
268
269
|
## License
|
|
269
270
|
|
|
270
271
|
Licensed under Apache-2.0.
|
|
272
|
+
## Dependency maintenance
|
|
273
|
+
|
|
274
|
+
See [dependency updates and Python tool groups](docs/dependencies.md) for locked tests, daily updates and freshness checks.
|
|
@@ -3,6 +3,7 @@ README.md
|
|
|
3
3
|
pyproject.toml
|
|
4
4
|
code2schema/__init__.py
|
|
5
5
|
code2schema/cli.py
|
|
6
|
+
code2schema/mcp_server.py
|
|
6
7
|
code2schema.egg-info/PKG-INFO
|
|
7
8
|
code2schema.egg-info/SOURCES.txt
|
|
8
9
|
code2schema.egg-info/dependency_links.txt
|
|
@@ -18,4 +19,5 @@ code2schema/codegen/visualizer.py
|
|
|
18
19
|
code2schema/core/__init__.py
|
|
19
20
|
code2schema/core/extractor.py
|
|
20
21
|
code2schema/core/models.py
|
|
21
|
-
tests/test_code2schema.py
|
|
22
|
+
tests/test_code2schema.py
|
|
23
|
+
tests/test_event_handlers.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code2schema"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.9"
|
|
8
8
|
description = "Semantic compiler: Code → AST → CQRS Model → Workflow DAG → Proto/Schema"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -19,18 +19,19 @@ dependencies = [
|
|
|
19
19
|
"networkx>=3.2",
|
|
20
20
|
"pydantic>=2.5",
|
|
21
21
|
]
|
|
22
|
-
|
|
23
|
-
[project.license]
|
|
24
|
-
text = "Apache-2.0"
|
|
22
|
+
license = "Apache-2.0"
|
|
25
23
|
|
|
26
24
|
[project.optional-dependencies]
|
|
27
25
|
proto = ["grpcio-tools>=1.60"]
|
|
28
26
|
neo4j = ["neo4j>=5.15"]
|
|
29
27
|
viz = ["pyvis>=0.3"]
|
|
30
|
-
dev = [
|
|
31
|
-
"
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=8",
|
|
30
|
+
"pytest-cov",
|
|
31
|
+
"black",
|
|
32
|
+
"ruff",
|
|
32
33
|
"costs>=0.1.20",
|
|
33
|
-
"pfix>=0.1.60",
|
|
34
|
+
"pfix>=0.1.60; python_version >= '3.10'",
|
|
34
35
|
]
|
|
35
36
|
|
|
36
37
|
[project.scripts]
|
|
@@ -75,3 +76,9 @@ max_commits = 500
|
|
|
75
76
|
|
|
76
77
|
# Cost thresholds for badge colors (USD)
|
|
77
78
|
badge_color_thresholds = { low = 1.0, medium = 5.0, high = 10.0, critical = 50.0 }
|
|
79
|
+
|
|
80
|
+
[tool.uv.dependency-groups]
|
|
81
|
+
automation = {requires-python = ">=3.12"}
|
|
82
|
+
|
|
83
|
+
[dependency-groups]
|
|
84
|
+
automation = ["goal>=2.2.0"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from code2schema.analyzer.events import DomainEvent, _find_handlers
|
|
5
|
+
from code2schema.core.models import FunctionIR, ModuleIR
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_handler_matching_preserves_order_duplicates_case_and_empty_keywords():
|
|
9
|
+
names = ["UserEvent", "UserEvent", "Event", "Entity1Event", "Entity10Event", "ŻółćEvent"]
|
|
10
|
+
events = [DomainEvent(name=n, emitted_by=str(i), handled_by=["existing"]) for i,n in enumerate(names)]
|
|
11
|
+
modules = [ModuleIR(name=m, path=m+".py", functions=[FunctionIR(name=n,module=m) for n in [
|
|
12
|
+
"handle_user", "ON_USER", "handle_entity10", "on_żółć", "plain_user", "consumer_user"]]) for m in ["a","b"]]
|
|
13
|
+
expected = copy.deepcopy(events)
|
|
14
|
+
for _ in range(2):
|
|
15
|
+
for mod in modules:
|
|
16
|
+
for f in mod.functions:
|
|
17
|
+
if re.search(r"(on_|handle_|listener|subscriber|consumer|process_|receive_)", f.name, re.I):
|
|
18
|
+
for ev in expected:
|
|
19
|
+
keyword = ev.name.lower().replace("event", "").strip()
|
|
20
|
+
if keyword and keyword in f.name.lower():
|
|
21
|
+
ev.handled_by.append(f.qualified_name)
|
|
22
|
+
_find_handlers(events, modules)
|
|
23
|
+
assert events == expected
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_empty_events_and_modules_are_noops():
|
|
27
|
+
events = [DomainEvent(name="UserEvent", emitted_by="emit")]
|
|
28
|
+
_find_handlers(events, [])
|
|
29
|
+
_find_handlers([], [])
|
|
30
|
+
assert events[0].handled_by == []
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|