modwire 1.1.1__tar.gz → 2.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.
- {modwire-1.1.1 → modwire-2.0.0}/CONTRIBUTING.md +2 -8
- modwire-2.0.0/PKG-INFO +252 -0
- modwire-2.0.0/README.md +219 -0
- {modwire-1.1.1 → modwire-2.0.0}/docs/wiki/Development-checks.md +3 -0
- {modwire-1.1.1 → modwire-2.0.0}/docs/wiki/Home.md +3 -0
- modwire-2.0.0/src/modwire/__init__.py +86 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/_version.py +3 -3
- modwire-2.0.0/src/modwire/architecture/__init__.py +59 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/architecture/analyzers.py +19 -0
- modwire-2.0.0/src/modwire/architecture/config.py +126 -0
- modwire-2.0.0/src/modwire/architecture/insights.py +246 -0
- modwire-2.0.0/src/modwire/architecture/matching.py +153 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/architecture/policy.py +20 -14
- modwire-2.0.0/src/modwire/architecture/render.py +99 -0
- modwire-2.0.0/src/modwire/architecture/violations.py +47 -0
- modwire-2.0.0/src/modwire/extraction/__init__.py +33 -0
- modwire-2.0.0/src/modwire/extraction/cache.py +92 -0
- modwire-2.0.0/src/modwire/extraction/manifest.py +97 -0
- modwire-2.0.0/src/modwire/extraction/models.py +151 -0
- modwire-2.0.0/src/modwire/extraction/roots.py +100 -0
- modwire-2.0.0/src/modwire/extraction/serialization.py +106 -0
- modwire-2.0.0/src/modwire/extraction/service.py +100 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/base.py +15 -2
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/loader.py +6 -1
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/php.py +5 -1
- modwire-2.0.0/src/modwire/graph.py +116 -0
- modwire-2.0.0/src/modwire/metadata.py +128 -0
- modwire-2.0.0/src/modwire/shape/__init__.py +16 -0
- modwire-2.0.0/src/modwire/shape/config.py +93 -0
- modwire-2.0.0/src/modwire/shape/evaluator.py +32 -0
- modwire-2.0.0/src/modwire/shape/rules.py +389 -0
- modwire-2.0.0/src/modwire/shape/violations.py +23 -0
- modwire-2.0.0/src/modwire/testing/__init__.py +24 -0
- modwire-2.0.0/src/modwire/testing/factories.py +168 -0
- modwire-2.0.0/src/modwire.egg-info/PKG-INFO +252 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire.egg-info/SOURCES.txt +17 -1
- {modwire-1.1.1 → modwire-2.0.0}/tests/test_api.py +440 -1
- modwire-2.0.0/tests/test_architecture_api.py +360 -0
- modwire-1.1.1/PKG-INFO +0 -127
- modwire-1.1.1/README.md +0 -94
- modwire-1.1.1/src/modwire/__init__.py +0 -18
- modwire-1.1.1/src/modwire/architecture/__init__.py +0 -10
- modwire-1.1.1/src/modwire/architecture/matching.py +0 -58
- modwire-1.1.1/src/modwire/architecture/render.py +0 -98
- modwire-1.1.1/src/modwire/architecture/violations.py +0 -24
- modwire-1.1.1/src/modwire/extraction.py +0 -73
- modwire-1.1.1/src/modwire/graph.py +0 -56
- modwire-1.1.1/src/modwire.egg-info/PKG-INFO +0 -127
- modwire-1.1.1/tests/test_architecture_api.py +0 -43
- {modwire-1.1.1 → modwire-2.0.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.github/workflows/ci.yml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.github/workflows/release.yml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/.gitignore +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/LICENSE +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/docs/wiki/Reporting-bugs.md +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/docs/wiki/Requesting-features.md +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/pyproject.toml +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/setup.cfg +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/show_test_source_files.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/definitions.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/exports.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/__init__.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/python.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/scripts/php_extractor.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/scripts/python_extractor.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/scripts/typescript_extractor.js +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire/extractors/typescript.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire.egg-info/dependency_links.txt +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire.egg-info/requires.txt +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/src/modwire.egg-info/top_level.txt +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/php/ignored/generated.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/php/src/application/use_cases/activate.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/php/src/domain/model/user.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/php/src/domain/services/policy.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/php/src/interfaces/http/controller.php +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/python/ignored/generated.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/python/src/application/use_cases/activate.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/python/src/domain/model/user.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/python/src/domain/services/policy.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/python/src/interfaces/http/controller.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/ignored/generated.ts +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/application/use_cases/activate.ts +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/domain/model/profile.tsx +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/domain/model/user.ts +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/domain/services/audit.js +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/domain/services/policy.ts +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/interfaces/http/controller.ts +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/apps/typescript/src/interfaces/http/view.jsx +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/tests/test_standalone.py +0 -0
- {modwire-1.1.1 → modwire-2.0.0}/uv.lock +0 -0
|
@@ -32,14 +32,8 @@ actual output, and relevant versions for Python, Modwire, Node.js, or PHP.
|
|
|
32
32
|
|
|
33
33
|
## Pull Requests
|
|
34
34
|
|
|
35
|
-
Before opening a pull request, run the local checks
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
uv run ruff check
|
|
39
|
-
uv run pytest
|
|
40
|
-
uv run python -m build --outdir dist
|
|
41
|
-
uv run twine check dist/*
|
|
42
|
-
```
|
|
35
|
+
Before opening a pull request, run the local checks documented in
|
|
36
|
+
[Development checks](docs/wiki/Development-checks.md).
|
|
43
37
|
|
|
44
38
|
Extractor changes should include focused tests under `tests/` and, when
|
|
45
39
|
relevant, a small fixture under `tests/apps/`.
|
modwire-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: modwire
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Extract source-code dependencies and build dependency graphs.
|
|
5
|
+
Author: Tomasz Szpak
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/9orky/modwire
|
|
8
|
+
Project-URL: Repository, https://github.com/9orky/modwire
|
|
9
|
+
Project-URL: Issues, https://github.com/9orky/modwire/issues
|
|
10
|
+
Keywords: architecture,code-analysis,dependency-graph,static-analysis
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: pydantic>=2.8
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=5.1; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# modwire
|
|
35
|
+
|
|
36
|
+
`modwire` extracts source-code structure and import dependencies from Python,
|
|
37
|
+
TypeScript/JavaScript, and PHP projects. It returns typed Python objects that
|
|
38
|
+
you can use to build dependency graphs, inspect symbols, and evaluate
|
|
39
|
+
architecture rules.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python -m pip install modwire
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The Python extractor works with Python alone. TypeScript/JavaScript extraction
|
|
48
|
+
requires Node.js at runtime, and PHP extraction requires PHP at runtime.
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from pathlib import Path
|
|
54
|
+
|
|
55
|
+
from modwire import discover_sources, extract_code
|
|
56
|
+
|
|
57
|
+
manifest = discover_sources(
|
|
58
|
+
"python",
|
|
59
|
+
Path("src"),
|
|
60
|
+
exclusions=("**/__pycache__/**",),
|
|
61
|
+
)
|
|
62
|
+
result = extract_code("python", Path("src"), exclusions=manifest.exclusions)
|
|
63
|
+
|
|
64
|
+
print(result.extraction_result.summary.files_checked)
|
|
65
|
+
print(result.graph.node_ids())
|
|
66
|
+
print([(edge.from_id, edge.to_id) for edge in result.graph.edges])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Graph nodes use canonical extensionless source IDs, so equivalent Python,
|
|
70
|
+
TypeScript, and PHP projects can be compared through the same graph shape.
|
|
71
|
+
|
|
72
|
+
## Supported Languages
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from modwire import supported_languages
|
|
77
|
+
|
|
78
|
+
print(supported_languages())
|
|
79
|
+
# ("python", "typescript", "php")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Language-specific source IDs can be normalized without running a full
|
|
83
|
+
extraction:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from modwire import normalize_source_id
|
|
87
|
+
|
|
88
|
+
print(normalize_source_id("typescript", "src/view.tsx"))
|
|
89
|
+
# "src/view"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Extraction Options
|
|
93
|
+
|
|
94
|
+
Use `SourceRoots` when source IDs should be relative to a workspace root or to a
|
|
95
|
+
logical package prefix:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pathlib import Path
|
|
99
|
+
|
|
100
|
+
from modwire import SourceRoots, extract_code
|
|
101
|
+
|
|
102
|
+
code_map = extract_code(
|
|
103
|
+
"python",
|
|
104
|
+
Path("packages/billing/src"),
|
|
105
|
+
source_roots=SourceRoots(
|
|
106
|
+
workspace_root=Path("."),
|
|
107
|
+
source_id_mode="relative_to_workspace_root",
|
|
108
|
+
),
|
|
109
|
+
)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Use `ExtractionCache` to reuse extraction output when files and extractor
|
|
113
|
+
implementations have not changed:
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from pathlib import Path
|
|
117
|
+
|
|
118
|
+
from modwire import ExtractionCache, extract_code
|
|
119
|
+
|
|
120
|
+
code_map = extract_code(
|
|
121
|
+
"python",
|
|
122
|
+
Path("src"),
|
|
123
|
+
cache=ExtractionCache(Path(".modwire-cache")),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
print(code_map.cache_status)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Architecture Policy API
|
|
130
|
+
|
|
131
|
+
`modwire.architecture` exposes policy evaluation helpers for checking import
|
|
132
|
+
boundaries and common dependency-flow rules.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from modwire import extract_code
|
|
136
|
+
from modwire.architecture import (
|
|
137
|
+
ArchitectureBoundaryRule,
|
|
138
|
+
ArchitectureConfig,
|
|
139
|
+
ArchitectureFlowRules,
|
|
140
|
+
ArchitecturePolicyEvaluator,
|
|
141
|
+
ArchitectureRules,
|
|
142
|
+
ArchitectureTagRule,
|
|
143
|
+
render_violations,
|
|
144
|
+
supported_analyzers,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
print(supported_analyzers())
|
|
148
|
+
# ("backward-flow", "no-reentry", "no-cycles")
|
|
149
|
+
|
|
150
|
+
code_map = extract_code("python", "src")
|
|
151
|
+
config = ArchitectureConfig(
|
|
152
|
+
language="python",
|
|
153
|
+
architecture_root="src",
|
|
154
|
+
rules=ArchitectureRules(
|
|
155
|
+
tags=(
|
|
156
|
+
ArchitectureTagRule(name="module", match="features/*"),
|
|
157
|
+
ArchitectureTagRule(name="ui", match="features/*/ui"),
|
|
158
|
+
ArchitectureTagRule(name="domain", match="features/*/domain"),
|
|
159
|
+
),
|
|
160
|
+
boundaries=(
|
|
161
|
+
ArchitectureBoundaryRule(
|
|
162
|
+
source="features/*/ui",
|
|
163
|
+
disallow=("features/*/domain",),
|
|
164
|
+
allow_same_match=True,
|
|
165
|
+
),
|
|
166
|
+
),
|
|
167
|
+
flow=ArchitectureFlowRules(
|
|
168
|
+
layers=("domain", "ui"),
|
|
169
|
+
module_tag="module",
|
|
170
|
+
analyzers=("no-cycles",),
|
|
171
|
+
),
|
|
172
|
+
),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
violations = ArchitecturePolicyEvaluator().evaluate(code_map.graph, config)
|
|
176
|
+
print(render_violations(tuple(violations)))
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Architecture insight helpers summarize ownership and graph pressure:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from modwire.architecture import coherence_summary, find_hotspots, map_code
|
|
183
|
+
|
|
184
|
+
architecture_map = map_code(code_map, config)
|
|
185
|
+
hotspots = find_hotspots(code_map, limit=5)
|
|
186
|
+
coherence = coherence_summary(code_map)
|
|
187
|
+
|
|
188
|
+
print(architecture_map.cross_module_dependencies)
|
|
189
|
+
print(hotspots)
|
|
190
|
+
print(coherence.external_dependencies)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Shape Policy API
|
|
194
|
+
|
|
195
|
+
Shape policies evaluate file, symbol, callable, property, and import metadata:
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from modwire import ShapePolicyEvaluator, evaluate_shape
|
|
199
|
+
|
|
200
|
+
violations = evaluate_shape(
|
|
201
|
+
code_map,
|
|
202
|
+
{
|
|
203
|
+
"max_functions_per_file": 5,
|
|
204
|
+
"max_methods_per_class": 10,
|
|
205
|
+
"allow_import_aliases": False,
|
|
206
|
+
"require_joined_imports": True,
|
|
207
|
+
},
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
same_result = ShapePolicyEvaluator().evaluate(code_map, {})
|
|
211
|
+
print([violation.to_dict() for violation in violations])
|
|
212
|
+
print(same_result)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Serialization And Exports
|
|
216
|
+
|
|
217
|
+
`CodeMap` results can be serialized for later analysis, and export metadata can
|
|
218
|
+
be used to find currently unused public symbols:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from modwire import (
|
|
222
|
+
deserialize_code_map,
|
|
223
|
+
find_unused_exports,
|
|
224
|
+
serialize_code_map,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
payload = serialize_code_map(code_map)
|
|
228
|
+
loaded = deserialize_code_map(payload)
|
|
229
|
+
|
|
230
|
+
unused = find_unused_exports(loaded.extraction_result)
|
|
231
|
+
print([(export.source_id, export.name) for export in unused])
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
See [Development checks](docs/wiki/Development-checks.md) for the local command
|
|
237
|
+
set used before pull requests and releases.
|
|
238
|
+
|
|
239
|
+
## Contributing
|
|
240
|
+
|
|
241
|
+
Feature requests and bug reports are tracked through GitHub Issues:
|
|
242
|
+
|
|
243
|
+
- Open a feature request for new language support, graph metadata, architecture
|
|
244
|
+
rules, export formats, or documentation examples.
|
|
245
|
+
- Open a bug report for incorrect extraction results, graph edges, architecture
|
|
246
|
+
violations, packaging problems, or runtime failures.
|
|
247
|
+
|
|
248
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the information to include and the
|
|
249
|
+
checks to run before opening a pull request.
|
|
250
|
+
|
|
251
|
+
Starter wiki pages are tracked under [docs/wiki](docs/wiki) so the GitHub Wiki
|
|
252
|
+
can be initialized with the same guidance.
|
modwire-2.0.0/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# modwire
|
|
2
|
+
|
|
3
|
+
`modwire` extracts source-code structure and import dependencies from Python,
|
|
4
|
+
TypeScript/JavaScript, and PHP projects. It returns typed Python objects that
|
|
5
|
+
you can use to build dependency graphs, inspect symbols, and evaluate
|
|
6
|
+
architecture rules.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
python -m pip install modwire
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The Python extractor works with Python alone. TypeScript/JavaScript extraction
|
|
15
|
+
requires Node.js at runtime, and PHP extraction requires PHP at runtime.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
from modwire import discover_sources, extract_code
|
|
23
|
+
|
|
24
|
+
manifest = discover_sources(
|
|
25
|
+
"python",
|
|
26
|
+
Path("src"),
|
|
27
|
+
exclusions=("**/__pycache__/**",),
|
|
28
|
+
)
|
|
29
|
+
result = extract_code("python", Path("src"), exclusions=manifest.exclusions)
|
|
30
|
+
|
|
31
|
+
print(result.extraction_result.summary.files_checked)
|
|
32
|
+
print(result.graph.node_ids())
|
|
33
|
+
print([(edge.from_id, edge.to_id) for edge in result.graph.edges])
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Graph nodes use canonical extensionless source IDs, so equivalent Python,
|
|
37
|
+
TypeScript, and PHP projects can be compared through the same graph shape.
|
|
38
|
+
|
|
39
|
+
## Supported Languages
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from modwire import supported_languages
|
|
44
|
+
|
|
45
|
+
print(supported_languages())
|
|
46
|
+
# ("python", "typescript", "php")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Language-specific source IDs can be normalized without running a full
|
|
50
|
+
extraction:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from modwire import normalize_source_id
|
|
54
|
+
|
|
55
|
+
print(normalize_source_id("typescript", "src/view.tsx"))
|
|
56
|
+
# "src/view"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Extraction Options
|
|
60
|
+
|
|
61
|
+
Use `SourceRoots` when source IDs should be relative to a workspace root or to a
|
|
62
|
+
logical package prefix:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from pathlib import Path
|
|
66
|
+
|
|
67
|
+
from modwire import SourceRoots, extract_code
|
|
68
|
+
|
|
69
|
+
code_map = extract_code(
|
|
70
|
+
"python",
|
|
71
|
+
Path("packages/billing/src"),
|
|
72
|
+
source_roots=SourceRoots(
|
|
73
|
+
workspace_root=Path("."),
|
|
74
|
+
source_id_mode="relative_to_workspace_root",
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Use `ExtractionCache` to reuse extraction output when files and extractor
|
|
80
|
+
implementations have not changed:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from pathlib import Path
|
|
84
|
+
|
|
85
|
+
from modwire import ExtractionCache, extract_code
|
|
86
|
+
|
|
87
|
+
code_map = extract_code(
|
|
88
|
+
"python",
|
|
89
|
+
Path("src"),
|
|
90
|
+
cache=ExtractionCache(Path(".modwire-cache")),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
print(code_map.cache_status)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Architecture Policy API
|
|
97
|
+
|
|
98
|
+
`modwire.architecture` exposes policy evaluation helpers for checking import
|
|
99
|
+
boundaries and common dependency-flow rules.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from modwire import extract_code
|
|
103
|
+
from modwire.architecture import (
|
|
104
|
+
ArchitectureBoundaryRule,
|
|
105
|
+
ArchitectureConfig,
|
|
106
|
+
ArchitectureFlowRules,
|
|
107
|
+
ArchitecturePolicyEvaluator,
|
|
108
|
+
ArchitectureRules,
|
|
109
|
+
ArchitectureTagRule,
|
|
110
|
+
render_violations,
|
|
111
|
+
supported_analyzers,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
print(supported_analyzers())
|
|
115
|
+
# ("backward-flow", "no-reentry", "no-cycles")
|
|
116
|
+
|
|
117
|
+
code_map = extract_code("python", "src")
|
|
118
|
+
config = ArchitectureConfig(
|
|
119
|
+
language="python",
|
|
120
|
+
architecture_root="src",
|
|
121
|
+
rules=ArchitectureRules(
|
|
122
|
+
tags=(
|
|
123
|
+
ArchitectureTagRule(name="module", match="features/*"),
|
|
124
|
+
ArchitectureTagRule(name="ui", match="features/*/ui"),
|
|
125
|
+
ArchitectureTagRule(name="domain", match="features/*/domain"),
|
|
126
|
+
),
|
|
127
|
+
boundaries=(
|
|
128
|
+
ArchitectureBoundaryRule(
|
|
129
|
+
source="features/*/ui",
|
|
130
|
+
disallow=("features/*/domain",),
|
|
131
|
+
allow_same_match=True,
|
|
132
|
+
),
|
|
133
|
+
),
|
|
134
|
+
flow=ArchitectureFlowRules(
|
|
135
|
+
layers=("domain", "ui"),
|
|
136
|
+
module_tag="module",
|
|
137
|
+
analyzers=("no-cycles",),
|
|
138
|
+
),
|
|
139
|
+
),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
violations = ArchitecturePolicyEvaluator().evaluate(code_map.graph, config)
|
|
143
|
+
print(render_violations(tuple(violations)))
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Architecture insight helpers summarize ownership and graph pressure:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from modwire.architecture import coherence_summary, find_hotspots, map_code
|
|
150
|
+
|
|
151
|
+
architecture_map = map_code(code_map, config)
|
|
152
|
+
hotspots = find_hotspots(code_map, limit=5)
|
|
153
|
+
coherence = coherence_summary(code_map)
|
|
154
|
+
|
|
155
|
+
print(architecture_map.cross_module_dependencies)
|
|
156
|
+
print(hotspots)
|
|
157
|
+
print(coherence.external_dependencies)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Shape Policy API
|
|
161
|
+
|
|
162
|
+
Shape policies evaluate file, symbol, callable, property, and import metadata:
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from modwire import ShapePolicyEvaluator, evaluate_shape
|
|
166
|
+
|
|
167
|
+
violations = evaluate_shape(
|
|
168
|
+
code_map,
|
|
169
|
+
{
|
|
170
|
+
"max_functions_per_file": 5,
|
|
171
|
+
"max_methods_per_class": 10,
|
|
172
|
+
"allow_import_aliases": False,
|
|
173
|
+
"require_joined_imports": True,
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
same_result = ShapePolicyEvaluator().evaluate(code_map, {})
|
|
178
|
+
print([violation.to_dict() for violation in violations])
|
|
179
|
+
print(same_result)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Serialization And Exports
|
|
183
|
+
|
|
184
|
+
`CodeMap` results can be serialized for later analysis, and export metadata can
|
|
185
|
+
be used to find currently unused public symbols:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from modwire import (
|
|
189
|
+
deserialize_code_map,
|
|
190
|
+
find_unused_exports,
|
|
191
|
+
serialize_code_map,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
payload = serialize_code_map(code_map)
|
|
195
|
+
loaded = deserialize_code_map(payload)
|
|
196
|
+
|
|
197
|
+
unused = find_unused_exports(loaded.extraction_result)
|
|
198
|
+
print([(export.source_id, export.name) for export in unused])
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Development
|
|
202
|
+
|
|
203
|
+
See [Development checks](docs/wiki/Development-checks.md) for the local command
|
|
204
|
+
set used before pull requests and releases.
|
|
205
|
+
|
|
206
|
+
## Contributing
|
|
207
|
+
|
|
208
|
+
Feature requests and bug reports are tracked through GitHub Issues:
|
|
209
|
+
|
|
210
|
+
- Open a feature request for new language support, graph metadata, architecture
|
|
211
|
+
rules, export formats, or documentation examples.
|
|
212
|
+
- Open a bug report for incorrect extraction results, graph edges, architecture
|
|
213
|
+
violations, packaging problems, or runtime failures.
|
|
214
|
+
|
|
215
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the information to include and the
|
|
216
|
+
checks to run before opening a pull request.
|
|
217
|
+
|
|
218
|
+
Starter wiki pages are tracked under [docs/wiki](docs/wiki) so the GitHub Wiki
|
|
219
|
+
can be initialized with the same guidance.
|
|
@@ -9,6 +9,9 @@ uv run python -m build --outdir dist
|
|
|
9
9
|
uv run twine check dist/*
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
This page is the canonical local-check list; README and contributor guidance
|
|
13
|
+
link here to avoid release-process drift.
|
|
14
|
+
|
|
12
15
|
Extractor changes should include focused tests under `tests/` and, when useful,
|
|
13
16
|
small source fixtures under `tests/apps/`.
|
|
14
17
|
|
|
@@ -7,6 +7,9 @@ rules.
|
|
|
7
7
|
|
|
8
8
|
## Start Here
|
|
9
9
|
|
|
10
|
+
- README examples cover extraction manifests, source roots, caching,
|
|
11
|
+
architecture policies, architecture insights, shape policies, serialization,
|
|
12
|
+
and unused export checks.
|
|
10
13
|
- [Reporting bugs](Reporting-bugs.md)
|
|
11
14
|
- [Requesting features](Requesting-features.md)
|
|
12
15
|
- [Development checks](Development-checks.md)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
from ._version import __version__
|
|
2
|
+
from .extraction import (
|
|
3
|
+
CodeMap,
|
|
4
|
+
CodeMapSerializationError,
|
|
5
|
+
ExtractionCache,
|
|
6
|
+
SourceChangedDuringExtractionError,
|
|
7
|
+
SourceManifest,
|
|
8
|
+
SourceManifestEntry,
|
|
9
|
+
SourceRoots,
|
|
10
|
+
deserialize_code_map,
|
|
11
|
+
discover_sources,
|
|
12
|
+
extract_code,
|
|
13
|
+
serialize_code_map,
|
|
14
|
+
)
|
|
15
|
+
from .extractors.loader import (
|
|
16
|
+
UnsupportedLanguageError,
|
|
17
|
+
normalize_source_id,
|
|
18
|
+
supported_languages,
|
|
19
|
+
)
|
|
20
|
+
from .exports import UnusedExport, find_unused_exports
|
|
21
|
+
from .graph import DependencyGraph, Edge, Node, build_dependency_graph
|
|
22
|
+
from .metadata import (
|
|
23
|
+
EXTRACTION_SCHEMA_VERSION,
|
|
24
|
+
PUBLIC_API_STABILITY,
|
|
25
|
+
ExtractorRuntimeError,
|
|
26
|
+
LanguageInfo,
|
|
27
|
+
MissingRuntimeError,
|
|
28
|
+
RuntimeInfo,
|
|
29
|
+
extraction_implementation_stamp,
|
|
30
|
+
language,
|
|
31
|
+
languages,
|
|
32
|
+
require_runtime,
|
|
33
|
+
runtime_diagnostics,
|
|
34
|
+
)
|
|
35
|
+
from .shape import (
|
|
36
|
+
ShapeConfig,
|
|
37
|
+
ShapeConfigError,
|
|
38
|
+
ShapeConfigIssue,
|
|
39
|
+
ShapePolicyEvaluator,
|
|
40
|
+
ShapeViolation,
|
|
41
|
+
evaluate_shape,
|
|
42
|
+
validate_shape_config,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"CodeMap",
|
|
48
|
+
"CodeMapSerializationError",
|
|
49
|
+
"DependencyGraph",
|
|
50
|
+
"Edge",
|
|
51
|
+
"EXTRACTION_SCHEMA_VERSION",
|
|
52
|
+
"ExtractionCache",
|
|
53
|
+
"ExtractorRuntimeError",
|
|
54
|
+
"LanguageInfo",
|
|
55
|
+
"MissingRuntimeError",
|
|
56
|
+
"Node",
|
|
57
|
+
"PUBLIC_API_STABILITY",
|
|
58
|
+
"RuntimeInfo",
|
|
59
|
+
"ShapeConfig",
|
|
60
|
+
"ShapeConfigError",
|
|
61
|
+
"ShapeConfigIssue",
|
|
62
|
+
"ShapePolicyEvaluator",
|
|
63
|
+
"ShapeViolation",
|
|
64
|
+
"SourceChangedDuringExtractionError",
|
|
65
|
+
"SourceManifest",
|
|
66
|
+
"SourceManifestEntry",
|
|
67
|
+
"SourceRoots",
|
|
68
|
+
"UnsupportedLanguageError",
|
|
69
|
+
"UnusedExport",
|
|
70
|
+
"__version__",
|
|
71
|
+
"build_dependency_graph",
|
|
72
|
+
"deserialize_code_map",
|
|
73
|
+
"discover_sources",
|
|
74
|
+
"evaluate_shape",
|
|
75
|
+
"extraction_implementation_stamp",
|
|
76
|
+
"extract_code",
|
|
77
|
+
"find_unused_exports",
|
|
78
|
+
"language",
|
|
79
|
+
"languages",
|
|
80
|
+
"normalize_source_id",
|
|
81
|
+
"require_runtime",
|
|
82
|
+
"runtime_diagnostics",
|
|
83
|
+
"serialize_code_map",
|
|
84
|
+
"supported_languages",
|
|
85
|
+
"validate_shape_config",
|
|
86
|
+
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '
|
|
22
|
-
__version_tuple__ = version_tuple = (
|
|
21
|
+
__version__ = version = '2.0.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'gb985fe227'
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from .analyzers import AnalyzerInfo, analyzer_metadata, supported_analyzers
|
|
2
|
+
from .config import (
|
|
3
|
+
ArchitectureBoundaryRule,
|
|
4
|
+
ArchitectureConfig,
|
|
5
|
+
ArchitectureConfigError,
|
|
6
|
+
ArchitectureConfigIssue,
|
|
7
|
+
ArchitectureFlowRules,
|
|
8
|
+
ArchitectureRules,
|
|
9
|
+
ArchitectureTagRule,
|
|
10
|
+
validate_policy_config,
|
|
11
|
+
)
|
|
12
|
+
from .insights import (
|
|
13
|
+
ArchitectureCluster,
|
|
14
|
+
ArchitectureMap,
|
|
15
|
+
CoherenceSummary,
|
|
16
|
+
CrossModuleDependency,
|
|
17
|
+
DependencyHotspot,
|
|
18
|
+
cluster_code,
|
|
19
|
+
coherence_summary,
|
|
20
|
+
find_hotspots,
|
|
21
|
+
map_code,
|
|
22
|
+
)
|
|
23
|
+
from .matching import TagMap, TagMatch, TagMatcher
|
|
24
|
+
from .policy import ArchitecturePolicyEvaluator
|
|
25
|
+
from .render import render_violation_payload, render_violations, structured_groups
|
|
26
|
+
from .violations import EdgeRuleViolation, FlowViolation, violation_to_dict
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"AnalyzerInfo",
|
|
30
|
+
"ArchitectureCluster",
|
|
31
|
+
"ArchitectureBoundaryRule",
|
|
32
|
+
"ArchitectureConfig",
|
|
33
|
+
"ArchitectureConfigError",
|
|
34
|
+
"ArchitectureConfigIssue",
|
|
35
|
+
"ArchitectureFlowRules",
|
|
36
|
+
"ArchitectureMap",
|
|
37
|
+
"ArchitecturePolicyEvaluator",
|
|
38
|
+
"ArchitectureRules",
|
|
39
|
+
"ArchitectureTagRule",
|
|
40
|
+
"CoherenceSummary",
|
|
41
|
+
"CrossModuleDependency",
|
|
42
|
+
"DependencyHotspot",
|
|
43
|
+
"EdgeRuleViolation",
|
|
44
|
+
"FlowViolation",
|
|
45
|
+
"TagMap",
|
|
46
|
+
"TagMatch",
|
|
47
|
+
"TagMatcher",
|
|
48
|
+
"analyzer_metadata",
|
|
49
|
+
"cluster_code",
|
|
50
|
+
"coherence_summary",
|
|
51
|
+
"find_hotspots",
|
|
52
|
+
"map_code",
|
|
53
|
+
"render_violation_payload",
|
|
54
|
+
"render_violations",
|
|
55
|
+
"structured_groups",
|
|
56
|
+
"supported_analyzers",
|
|
57
|
+
"validate_policy_config",
|
|
58
|
+
"violation_to_dict",
|
|
59
|
+
]
|
|
@@ -13,10 +13,29 @@ class FlowAnalyzer:
|
|
|
13
13
|
run: Callable
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class AnalyzerInfo:
|
|
18
|
+
name: str
|
|
19
|
+
title: str
|
|
20
|
+
|
|
21
|
+
def to_dict(self) -> dict[str, str]:
|
|
22
|
+
return {
|
|
23
|
+
"name": self.name,
|
|
24
|
+
"title": self.title,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
16
28
|
def supported_analyzers() -> tuple[str, ...]:
|
|
17
29
|
return tuple(_ANALYZERS)
|
|
18
30
|
|
|
19
31
|
|
|
32
|
+
def analyzer_metadata() -> tuple[AnalyzerInfo, ...]:
|
|
33
|
+
return tuple(
|
|
34
|
+
AnalyzerInfo(name=analyzer.name, title=analyzer.title)
|
|
35
|
+
for analyzer in _ANALYZERS.values()
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
20
39
|
def analyzer_title(name: str) -> str:
|
|
21
40
|
return _ANALYZERS[name].title
|
|
22
41
|
|