modwire-cli 1.1.0__tar.gz → 1.2.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_cli-1.2.0/.github/workflows/ci.yml +17 -0
- modwire_cli-1.2.0/.github/workflows/release.yml +40 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/.gitignore +1 -0
- modwire_cli-1.2.0/.modwire/INDEX.md +10 -0
- modwire_cli-1.2.0/.modwire/architecture.yaml +38 -0
- modwire_cli-1.2.0/.modwire/docs/ARCHITECTURE.md +34 -0
- modwire_cli-1.2.0/.modwire/docs/MODWIRE.md +5 -0
- modwire_cli-1.2.0/.modwire/docs/PROJECT.md +16 -0
- modwire_cli-1.2.0/.modwire/docs/RULES.md +6 -0
- modwire_cli-1.2.0/.modwire/docs/TESTING.md +7 -0
- modwire_cli-1.2.0/.modwire/docs/WORKFLOW.md +8 -0
- modwire_cli-1.2.0/AGENTS.md +1 -0
- modwire_cli-1.2.0/Makefile +14 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/PKG-INFO +44 -14
- modwire_cli-1.2.0/README.md +73 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/pyproject.toml +15 -14
- modwire_cli-1.2.0/scripts/generate_docs.py +4 -0
- modwire_cli-1.2.0/setup.cfg +4 -0
- modwire_cli-1.2.0/src/modwire_cli/__main__.py +4 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/_version.py +3 -3
- modwire_cli-1.2.0/src/modwire_cli/cli.py +63 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/config.py +2 -0
- modwire_cli-1.2.0/src/modwire_cli/documentation/__init__.py +4 -0
- modwire_cli-1.2.0/src/modwire_cli/documentation/command.py +23 -0
- modwire_cli-1.2.0/src/modwire_cli/documentation/generator.py +47 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/extraction.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/facade.py +3 -1
- modwire_cli-1.2.0/src/modwire_cli/initialization/__init__.py +12 -0
- modwire_cli-1.2.0/src/modwire_cli/initialization/asset.py +26 -0
- modwire_cli-1.2.0/src/modwire_cli/initialization/facade.py +34 -0
- modwire_cli-1.2.0/src/modwire_cli/initialization/result.py +13 -0
- modwire_cli-1.2.0/src/modwire_cli/initialization/service.py +47 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/context.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/runner.py +3 -1
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/step.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/callables_insight.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/clusters_insight.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/coherence_insight.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/flow_violations.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/hotspots_insight.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/map_tree.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/shape_violations.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/success.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/unknown_files_warning.py +2 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/unused_exports_insight.py +2 -0
- modwire_cli-1.2.0/src/modwire_cli/py.typed +1 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/AGENTS.md +1 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/ARCHITECTURE.md +34 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/INDEX.md +9 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/MODWIRE.md +5 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/RULES.md +6 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/TESTING.md +7 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/WORKFLOW.md +8 -0
- modwire_cli-1.2.0/src/modwire_cli/resources/init/architecture.yaml +56 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/PKG-INFO +108 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/SOURCES.txt +74 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/entry_points.txt +2 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/requires.txt +11 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/scm_file_list.json +69 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/scm_version.json +8 -0
- modwire_cli-1.2.0/src/modwire_cli.egg-info/top_level.txt +1 -0
- modwire_cli-1.2.0/tests/__init__.py +0 -0
- modwire_cli-1.2.0/tests/functional/__init__.py +0 -0
- modwire_cli-1.2.0/tests/functional/support.py +44 -0
- modwire_cli-1.2.0/tests/functional/test_01_attacks.py +13 -0
- modwire_cli-1.2.0/tests/functional/test_02_invariants.py +37 -0
- modwire_cli-1.2.0/tests/functional/test_03_interruption.py +18 -0
- modwire_cli-1.2.0/tests/functional/test_04_cleanup.py +18 -0
- modwire_cli-1.2.0/tests/functional/test_05_recovery.py +16 -0
- modwire_cli-1.2.0/tests/functional/test_06_happy_path.py +31 -0
- modwire_cli-1.2.0/uv.lock +769 -0
- modwire_cli-1.1.0/README.md +0 -43
- modwire_cli-1.1.0/src/modwire_cli/__main__.py +0 -44
- modwire_cli-1.1.0/tests/test_cli.py +0 -122
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/LICENSE +0 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/__init__.py +0 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/__init__.py +0 -0
- {modwire_cli-1.1.0 → modwire_cli-1.2.0}/src/modwire_cli/pipeline/steps/__init__.py +0 -0
- /modwire_cli-1.1.0/src/modwire_cli/py.typed → /modwire_cli-1.2.0/src/modwire_cli.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
verify:
|
|
14
|
+
uses: modwire/modwire-architecture/.github/workflows/python-package.yml@workflows-v1
|
|
15
|
+
with:
|
|
16
|
+
python-versions: '["3.12", "3.13", "3.14"]'
|
|
17
|
+
build-python-version: "3.12"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
uses: modwire/modwire-architecture/.github/workflows/python-release-build.yml@workflows-v1
|
|
13
|
+
with:
|
|
14
|
+
release-tag: ${{ github.event.release.tag_name }}
|
|
15
|
+
python-version: "3.12"
|
|
16
|
+
|
|
17
|
+
publish-github-assets:
|
|
18
|
+
needs: build
|
|
19
|
+
uses: modwire/modwire-architecture/.github/workflows/python-release-assets.yml@workflows-v1
|
|
20
|
+
with:
|
|
21
|
+
release-tag: ${{ github.event.release.tag_name }}
|
|
22
|
+
permissions:
|
|
23
|
+
contents: write
|
|
24
|
+
|
|
25
|
+
publish-pypi:
|
|
26
|
+
needs: [build, publish-github-assets]
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
environment:
|
|
29
|
+
name: pypi
|
|
30
|
+
url: https://pypi.org/project/modwire-cli/
|
|
31
|
+
permissions:
|
|
32
|
+
id-token: write
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/download-artifact@v8
|
|
35
|
+
with:
|
|
36
|
+
name: python-package-distributions
|
|
37
|
+
path: dist/
|
|
38
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
39
|
+
with:
|
|
40
|
+
skip-existing: true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Modwire
|
|
2
|
+
|
|
3
|
+
Modwire keeps software changes aligned with a project's architecture, working rules, testing expectations, and delivery workflow.
|
|
4
|
+
|
|
5
|
+
- [Architecture configuration](docs/ARCHITECTURE.md)
|
|
6
|
+
- [Quality loop](docs/MODWIRE.md)
|
|
7
|
+
- [Project](docs/PROJECT.md)
|
|
8
|
+
- [Working rules](docs/RULES.md)
|
|
9
|
+
- [Testing](docs/TESTING.md)
|
|
10
|
+
- [GitHub workflow](docs/WORKFLOW.md)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
boundaries:
|
|
2
|
+
tags:
|
|
3
|
+
- name: package
|
|
4
|
+
match: src/modwire_cli
|
|
5
|
+
- name: tests
|
|
6
|
+
match: tests
|
|
7
|
+
- name: scripts
|
|
8
|
+
match: scripts
|
|
9
|
+
flow:
|
|
10
|
+
module_tag: package
|
|
11
|
+
realms:
|
|
12
|
+
- name: tests
|
|
13
|
+
module_tag: tests
|
|
14
|
+
- name: scripts
|
|
15
|
+
module_tag: scripts
|
|
16
|
+
analyzers:
|
|
17
|
+
- module-boundaries
|
|
18
|
+
- no-cycles
|
|
19
|
+
- no-reentry
|
|
20
|
+
shape:
|
|
21
|
+
max_classes_per_file: 1
|
|
22
|
+
max_interfaces_per_file: 0
|
|
23
|
+
max_types_per_file: 0
|
|
24
|
+
max_abstract_classes_per_file: -1
|
|
25
|
+
max_functions_per_file: 0
|
|
26
|
+
max_methods_per_class: -1
|
|
27
|
+
max_declared_args: -1
|
|
28
|
+
max_function_lines: -1
|
|
29
|
+
max_method_lines: -1
|
|
30
|
+
max_class_lines: -1
|
|
31
|
+
allow_optional_function_args: false
|
|
32
|
+
allow_optional_method_args: false
|
|
33
|
+
allow_optional_class_properties: false
|
|
34
|
+
allow_import_aliases: false
|
|
35
|
+
require_joined_imports: true
|
|
36
|
+
allowed_import_crossing_types:
|
|
37
|
+
- module
|
|
38
|
+
- symbol
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Architecture configuration
|
|
2
|
+
|
|
3
|
+
`architecture.yaml` describes the source-tree boundaries and structural limits that Modwire checks. Adapt its paths and names before enforcing it in a project.
|
|
4
|
+
|
|
5
|
+
## Tags
|
|
6
|
+
|
|
7
|
+
Each tag gives a source-tree pattern a meaningful name. The example uses compact YAML mappings to keep the relationship visible:
|
|
8
|
+
|
|
9
|
+
```yaml
|
|
10
|
+
tags:
|
|
11
|
+
- {name: &module_tag module, match: src/*}
|
|
12
|
+
- {name: &shared_tag shared, match: src/shared}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`&module_tag` anchors the scalar value `module`; `*module_tag` inserts that same string elsewhere in the file. This avoids repeating tag names:
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
rules:
|
|
19
|
+
- {source: *module_tag, disallow: [*module_tag], allow: [*shared_tag], allow_same_match: true}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Anchor the `name` value, not the whole tag object. Rule `source` and `allow` fields, and flow `module_tag` and `layers` fields, accept tag-name strings rather than `{name, match}` objects.
|
|
23
|
+
|
|
24
|
+
## Rules and flow
|
|
25
|
+
|
|
26
|
+
Rules describe which tagged modules may depend on one another. Flow groups tags into a module realm and, when needed, orders its layers:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
flow:
|
|
30
|
+
realms:
|
|
31
|
+
- {name: backend, module_tag: *backend_module_tag, layers: [*api_tag, *application_tag, *infrastructure_tag, *domain_tag]}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Use aliases consistently when a tag name appears in more than one place. The supplied `architecture.yaml` is a complete, adaptable example; keep its shape limits only when they suit the project.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# modwire-cli
|
|
2
|
+
|
|
3
|
+
Python 3.12+ CLI for Modwire architecture reports.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `__main__.py`: argument parsing and dependency-container lifecycle.
|
|
8
|
+
- `facade.py`: load config, extract code, request reports, run the output pipeline.
|
|
9
|
+
- `pipeline/steps/`: independently rendered report sections, ordered by Wireup qualifiers.
|
|
10
|
+
- `.modwire/architecture.yaml`: strict `ArchitectureConfig` input; unknown fields must remain invalid.
|
|
11
|
+
|
|
12
|
+
## Working rules
|
|
13
|
+
|
|
14
|
+
- Preserve `modwire --language <language>` report behavior unless a change explicitly replaces it.
|
|
15
|
+
- Keep CLI options at the boundary; put behavior in injected services/facades.
|
|
16
|
+
- Add subprocess-level coverage for every user-facing command in `tests/test_cli.py`.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Working rules
|
|
2
|
+
|
|
3
|
+
- Use comments only as docstrings that document public API classes or functions.
|
|
4
|
+
- Keep documentation focused on its readers' tasks; do not generate internal implementation inventories for end users.
|
|
5
|
+
- When running local CLI commands, use host mode or an execution mode with privileges equivalent to the user's.
|
|
6
|
+
- Read the local guidance before changing code.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
If a local Modwire MCP tool is available, consult it first for project-specific testing rules.
|
|
4
|
+
|
|
5
|
+
Test only public interfaces and assert observable outcomes such as returned values, exit codes, output, or files.
|
|
6
|
+
|
|
7
|
+
Write scenarios in Auntie order: attacks, invariants, interruption, cleanup, recovery, then happy paths.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# GitHub workflow
|
|
2
|
+
|
|
3
|
+
1. Start with both the local and remote workspaces clean and with only the `main` branch.
|
|
4
|
+
2. Make changes only in the local workspace, under user supervision.
|
|
5
|
+
3. The user may create multiple local checkpoint commits while the work is in progress.
|
|
6
|
+
4. Before pushing the feature branch, squash all of its current commits into one commit with a clear, appropriate message.
|
|
7
|
+
5. Submit a pull request. It becomes ready to merge only after its QA checks succeed.
|
|
8
|
+
6. After the user merges the pull request, clean up both workspaces so that only the `main` branch remains.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Check `.modwire/INDEX.md` before making changes.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.PHONY: docs docs-check verify
|
|
2
|
+
|
|
3
|
+
check:
|
|
4
|
+
uv run modwire --language python --summary
|
|
5
|
+
|
|
6
|
+
docs:
|
|
7
|
+
uv run python scripts/generate_docs.py
|
|
8
|
+
|
|
9
|
+
docs-check:
|
|
10
|
+
uv run python scripts/generate_docs.py --check
|
|
11
|
+
|
|
12
|
+
verify: docs-check
|
|
13
|
+
uv run ruff check .
|
|
14
|
+
uv run pytest
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: modwire-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Command-line interface for the Modwire ecosystem.
|
|
5
|
+
Author: Tomasz Szpak
|
|
6
|
+
License-Expression: MIT
|
|
5
7
|
Project-URL: Homepage, https://github.com/modwire/modwire-cli
|
|
6
8
|
Project-URL: Repository, https://github.com/modwire/modwire-cli
|
|
7
9
|
Project-URL: Issues, https://github.com/modwire/modwire-cli/issues
|
|
8
|
-
Author: Tomasz Szpak
|
|
9
|
-
License-Expression: MIT
|
|
10
|
-
License-File: LICENSE
|
|
11
10
|
Keywords: cli,modwire
|
|
12
11
|
Classifier: Development Status :: 3 - Alpha
|
|
13
12
|
Classifier: Intended Audience :: Developers
|
|
14
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
15
13
|
Classifier: Operating System :: OS Independent
|
|
16
14
|
Classifier: Programming Language :: Python
|
|
17
15
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -21,17 +19,19 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
21
19
|
Classifier: Programming Language :: Python :: 3.14
|
|
22
20
|
Classifier: Topic :: Software Development
|
|
23
21
|
Requires-Python: >=3.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
24
|
Requires-Dist: modwire-architecture<7,>=6
|
|
25
25
|
Requires-Dist: modwire-extraction<3,>=2
|
|
26
26
|
Requires-Dist: pydantic-yaml>=1.7
|
|
27
27
|
Requires-Dist: rich>=13
|
|
28
28
|
Requires-Dist: wireup>=2.12.0
|
|
29
29
|
Provides-Extra: dev
|
|
30
|
-
Requires-Dist: build>=1.3; extra ==
|
|
31
|
-
Requires-Dist: pytest>=9; extra ==
|
|
32
|
-
Requires-Dist: ruff>=0.14; extra ==
|
|
33
|
-
Requires-Dist: twine>=6; extra ==
|
|
34
|
-
|
|
30
|
+
Requires-Dist: build>=1.3; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest>=9; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff>=0.14; extra == "dev"
|
|
33
|
+
Requires-Dist: twine>=6; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
35
|
|
|
36
36
|
# modwire-cli
|
|
37
37
|
|
|
@@ -46,28 +46,51 @@ pip install modwire-cli
|
|
|
46
46
|
## Usage
|
|
47
47
|
|
|
48
48
|
```sh
|
|
49
|
-
modwire
|
|
49
|
+
modwire init
|
|
50
|
+
modwire report --language python
|
|
50
51
|
```
|
|
51
52
|
|
|
52
53
|
By default, Modwire reads its configuration from `.modwire/architecture.yaml`
|
|
53
54
|
and analyses the current directory. Override either path when needed:
|
|
54
55
|
|
|
55
56
|
```sh
|
|
56
|
-
modwire --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
57
|
+
modwire report --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
Use `--summary` for a compact module → layer map that omits individual source files:
|
|
60
61
|
|
|
61
62
|
```sh
|
|
62
|
-
modwire --language python --summary
|
|
63
|
+
modwire report --language python --summary
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The original option-only command remains supported for existing automation:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
modwire --language python
|
|
63
70
|
```
|
|
64
71
|
|
|
65
72
|
To check this repository's architecture:
|
|
66
73
|
|
|
67
74
|
```sh
|
|
68
|
-
uv run modwire --language python
|
|
75
|
+
uv run modwire report --language python
|
|
69
76
|
```
|
|
70
77
|
|
|
78
|
+
<!-- generated:public-api:start -->
|
|
79
|
+
## Command reference
|
|
80
|
+
|
|
81
|
+
Provide `modwire init` for setup and `modwire report` for architecture feedback.
|
|
82
|
+
|
|
83
|
+
Existing `modwire --language <language>` automation continues to run reports.
|
|
84
|
+
|
|
85
|
+
| Command | Purpose |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `modwire init` | Create `.modwire/` guidance and a strict architecture template. |
|
|
88
|
+
| `modwire report --language <language>` | Analyse the configured project and render violations. |
|
|
89
|
+
| `modwire --language <language>` | Backwards-compatible form of `report`. |
|
|
90
|
+
|
|
91
|
+
Use `--summary` with `report` to render module-to-layer membership without files.
|
|
92
|
+
<!-- generated:public-api:end -->
|
|
93
|
+
|
|
71
94
|
## Development
|
|
72
95
|
|
|
73
96
|
```sh
|
|
@@ -76,3 +99,10 @@ uv run pytest
|
|
|
76
99
|
uv run ruff check .
|
|
77
100
|
uv build
|
|
78
101
|
```
|
|
102
|
+
|
|
103
|
+
The README command reference is generated from the public CLI docstring:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
make docs
|
|
107
|
+
make verify
|
|
108
|
+
```
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# modwire-cli
|
|
2
|
+
|
|
3
|
+
The command-line interface for the Modwire ecosystem.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pip install modwire-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
modwire init
|
|
15
|
+
modwire report --language python
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
By default, Modwire reads its configuration from `.modwire/architecture.yaml`
|
|
19
|
+
and analyses the current directory. Override either path when needed:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
modwire report --language typescript --dot-dir path/to/.modwire --architecture-root path/to/source
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Use `--summary` for a compact module → layer map that omits individual source files:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
modwire report --language python --summary
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The original option-only command remains supported for existing automation:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
modwire --language python
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
To check this repository's architecture:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
uv run modwire report --language python
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
<!-- generated:public-api:start -->
|
|
44
|
+
## Command reference
|
|
45
|
+
|
|
46
|
+
Provide `modwire init` for setup and `modwire report` for architecture feedback.
|
|
47
|
+
|
|
48
|
+
Existing `modwire --language <language>` automation continues to run reports.
|
|
49
|
+
|
|
50
|
+
| Command | Purpose |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `modwire init` | Create `.modwire/` guidance and a strict architecture template. |
|
|
53
|
+
| `modwire report --language <language>` | Analyse the configured project and render violations. |
|
|
54
|
+
| `modwire --language <language>` | Backwards-compatible form of `report`. |
|
|
55
|
+
|
|
56
|
+
Use `--summary` with `report` to render module-to-layer membership without files.
|
|
57
|
+
<!-- generated:public-api:end -->
|
|
58
|
+
|
|
59
|
+
## Development
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
uv sync --all-groups
|
|
63
|
+
uv run pytest
|
|
64
|
+
uv run ruff check .
|
|
65
|
+
uv build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The README command reference is generated from the public CLI docstring:
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
make docs
|
|
72
|
+
make verify
|
|
73
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["
|
|
3
|
-
build-backend = "
|
|
2
|
+
requires = ["setuptools>=77", "setuptools-scm[toml]>=8", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "modwire-cli"
|
|
@@ -13,7 +13,6 @@ authors = [{ name = "Tomasz Szpak" }]
|
|
|
13
13
|
classifiers = [
|
|
14
14
|
"Development Status :: 3 - Alpha",
|
|
15
15
|
"Intended Audience :: Developers",
|
|
16
|
-
"License :: OSI Approved :: MIT License",
|
|
17
16
|
"Operating System :: OS Independent",
|
|
18
17
|
"Programming Language :: Python",
|
|
19
18
|
"Programming Language :: Python :: 3",
|
|
@@ -33,7 +32,7 @@ dependencies = [
|
|
|
33
32
|
]
|
|
34
33
|
|
|
35
34
|
[project.scripts]
|
|
36
|
-
modwire = "modwire_cli.
|
|
35
|
+
modwire = "modwire_cli.cli:main"
|
|
37
36
|
|
|
38
37
|
[project.optional-dependencies]
|
|
39
38
|
dev = [
|
|
@@ -56,19 +55,21 @@ dev = [
|
|
|
56
55
|
"twine>=6",
|
|
57
56
|
]
|
|
58
57
|
|
|
59
|
-
[tool.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fallback-version = "0.0.0"
|
|
58
|
+
[tool.setuptools]
|
|
59
|
+
package-dir = { "" = "src" }
|
|
60
|
+
include-package-data = true
|
|
63
61
|
|
|
64
|
-
[tool.
|
|
65
|
-
|
|
62
|
+
[tool.setuptools.packages.find]
|
|
63
|
+
where = ["src"]
|
|
66
64
|
|
|
67
|
-
[tool.
|
|
68
|
-
|
|
65
|
+
[tool.setuptools.package-data]
|
|
66
|
+
"modwire_cli" = ["py.typed", "resources/init/*"]
|
|
69
67
|
|
|
70
|
-
[tool.
|
|
71
|
-
|
|
68
|
+
[tool.setuptools_scm]
|
|
69
|
+
tag_regex = '^v(?P<version>\d+\.\d+\.\d+)$'
|
|
70
|
+
fallback_version = "0.0.0.dev0"
|
|
71
|
+
local_scheme = "no-local-version"
|
|
72
|
+
version_file = "src/modwire_cli/_version.py"
|
|
72
73
|
|
|
73
74
|
[tool.pytest.ini_options]
|
|
74
75
|
testpaths = ["tests"]
|
|
@@ -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 = '1.
|
|
22
|
-
__version_tuple__ = version_tuple = (1,
|
|
21
|
+
__version__ = version = '1.2.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 2, 0)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id =
|
|
24
|
+
__commit_id__ = commit_id = 'g5c68c8d1a'
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from wireup import create_sync_container, instance
|
|
10
|
+
|
|
11
|
+
import modwire_cli
|
|
12
|
+
|
|
13
|
+
from .facade import FitnessFacade
|
|
14
|
+
from .initialization import InitializationFacade
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CommandLine:
|
|
18
|
+
"""Provide `modwire init` for setup and `modwire report` for architecture feedback.
|
|
19
|
+
|
|
20
|
+
Existing `modwire --language <language>` automation continues to run reports.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __call__(self) -> int:
|
|
24
|
+
"""Run the command supplied by the process argument vector."""
|
|
25
|
+
return self.run(sys.argv[1:])
|
|
26
|
+
|
|
27
|
+
def run(self, argv: Sequence[str]) -> int:
|
|
28
|
+
"""Run one command and return its shell-compatible status code."""
|
|
29
|
+
arguments = self._parser().parse_args(self._legacy_report_arguments(argv))
|
|
30
|
+
container = create_sync_container(injectables=[modwire_cli, instance(Console(), as_type=Console)])
|
|
31
|
+
try:
|
|
32
|
+
if arguments.command == "init":
|
|
33
|
+
return container.get(InitializationFacade).initialize(Path.cwd(), arguments.dot_dir, arguments.force)
|
|
34
|
+
return container.get(FitnessFacade).reports(
|
|
35
|
+
arguments.dot_dir,
|
|
36
|
+
arguments.architecture_root,
|
|
37
|
+
arguments.language,
|
|
38
|
+
arguments.summary,
|
|
39
|
+
)
|
|
40
|
+
finally:
|
|
41
|
+
container.close()
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def _parser() -> argparse.ArgumentParser:
|
|
45
|
+
parser = argparse.ArgumentParser()
|
|
46
|
+
commands = parser.add_subparsers(dest="command", required=True)
|
|
47
|
+
init = commands.add_parser("init", help="Create project-local Modwire configuration.")
|
|
48
|
+
init.add_argument("--dot-dir", type=Path, default=Path(".modwire"))
|
|
49
|
+
init.add_argument("--force", action="store_true", help="Replace existing generated assets.")
|
|
50
|
+
report = commands.add_parser("report", help="Run architecture reports.")
|
|
51
|
+
report.add_argument("--dot-dir", type=Path, default=Path(".modwire"))
|
|
52
|
+
report.add_argument("--architecture-root", type=Path, default=Path("."))
|
|
53
|
+
report.add_argument("--language", required=True)
|
|
54
|
+
report.add_argument("--summary", action="store_true", help="Render modules without individual files.")
|
|
55
|
+
return parser
|
|
56
|
+
|
|
57
|
+
@staticmethod
|
|
58
|
+
def _legacy_report_arguments(argv: Sequence[str]) -> Sequence[str]:
|
|
59
|
+
command_names = {"init", "report", "-h", "--help"}
|
|
60
|
+
return ("report", *argv) if argv and argv[0] not in command_names else argv
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
main = CommandLine()
|
|
@@ -11,5 +11,7 @@ from wireup import injectable
|
|
|
11
11
|
@injectable
|
|
12
12
|
@dataclass(frozen=True)
|
|
13
13
|
class ConfigService:
|
|
14
|
+
"""Load validated architecture configuration from the project dot directory."""
|
|
15
|
+
|
|
14
16
|
def load(self, dot_dir: Path) -> ArchitectureConfig:
|
|
15
17
|
return parse_yaml_file_as(ArchitectureConfig, dot_dir / "architecture.yaml")
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .generator import DocumentationGenerator
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DocumentationCommand:
|
|
10
|
+
"""Update or validate the generated README class reference."""
|
|
11
|
+
|
|
12
|
+
def run(self) -> int:
|
|
13
|
+
"""Run the documentation command from the process argument vector."""
|
|
14
|
+
parser = argparse.ArgumentParser(description="Generate README class reference documentation.")
|
|
15
|
+
parser.add_argument("--check", action="store_true", help="Fail instead of updating stale documentation.")
|
|
16
|
+
arguments = parser.parse_args()
|
|
17
|
+
readme = Path(__file__).parents[3] / "README.md"
|
|
18
|
+
if DocumentationGenerator().update(readme, arguments.check):
|
|
19
|
+
return 0
|
|
20
|
+
if arguments.check:
|
|
21
|
+
print("Generated documentation is stale. Run `make docs` and commit the result.")
|
|
22
|
+
return 1
|
|
23
|
+
return 0
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from inspect import cleandoc
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from ..cli import CommandLine
|
|
7
|
+
|
|
8
|
+
START = "<!-- generated:public-api:start -->"
|
|
9
|
+
END = "<!-- generated:public-api:end -->"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class DocumentationGenerator:
|
|
13
|
+
"""Render a concise README command reference from the public CLI docstring."""
|
|
14
|
+
|
|
15
|
+
def render(self) -> str:
|
|
16
|
+
"""Render the marked README section for people using the CLI."""
|
|
17
|
+
return "\n".join(
|
|
18
|
+
(
|
|
19
|
+
START,
|
|
20
|
+
"## Command reference",
|
|
21
|
+
"",
|
|
22
|
+
cleandoc(CommandLine.__doc__ or ""),
|
|
23
|
+
"",
|
|
24
|
+
"| Command | Purpose |",
|
|
25
|
+
"| --- | --- |",
|
|
26
|
+
"| `modwire init` | Create `.modwire/` guidance and a strict architecture template. |",
|
|
27
|
+
"| `modwire report --language <language>` | Analyse the configured project and render violations. |",
|
|
28
|
+
"| `modwire --language <language>` | Backwards-compatible form of `report`. |",
|
|
29
|
+
"",
|
|
30
|
+
"Use `--summary` with `report` to render module-to-layer membership without files.",
|
|
31
|
+
END,
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
def update(self, readme: Path, check: bool) -> bool:
|
|
36
|
+
"""Update the README section, or report whether it is current."""
|
|
37
|
+
current = readme.read_text(encoding="utf-8")
|
|
38
|
+
if START not in current or END not in current:
|
|
39
|
+
raise ValueError(f"Missing generated documentation markers in {readme}")
|
|
40
|
+
prefix, remainder = current.split(START, 1)
|
|
41
|
+
_, suffix = remainder.split(END, 1)
|
|
42
|
+
expected = f"{prefix}{self.render()}{suffix}"
|
|
43
|
+
if current == expected:
|
|
44
|
+
return True
|
|
45
|
+
if not check:
|
|
46
|
+
readme.write_text(expected, encoding="utf-8")
|
|
47
|
+
return False
|
|
@@ -10,5 +10,7 @@ from wireup import injectable
|
|
|
10
10
|
@injectable
|
|
11
11
|
@dataclass(frozen=True)
|
|
12
12
|
class ExtractionService:
|
|
13
|
+
"""Extract a queryable code map for one source root and language."""
|
|
14
|
+
|
|
13
15
|
def load(self, architecture_root: Path, language: str) -> QueryableCodeMap:
|
|
14
16
|
return ModwireExtraction(architecture_root).generate_queryable_map(language)
|
|
@@ -14,6 +14,8 @@ from .pipeline.runner import ReportPipelineRunner
|
|
|
14
14
|
@injectable
|
|
15
15
|
@dataclass(frozen=True)
|
|
16
16
|
class FitnessFacade:
|
|
17
|
+
"""Coordinate configuration, extraction, reporting, and report rendering."""
|
|
18
|
+
|
|
17
19
|
config_service: ConfigService
|
|
18
20
|
extraction_service: ExtractionService
|
|
19
21
|
pipeline_runner: ReportPipelineRunner
|
|
@@ -23,7 +25,7 @@ class FitnessFacade:
|
|
|
23
25
|
dot_dir: Path,
|
|
24
26
|
architecture_root: Path,
|
|
25
27
|
language: str,
|
|
26
|
-
summary: bool
|
|
28
|
+
summary: bool,
|
|
27
29
|
) -> int:
|
|
28
30
|
config = self.config_service.load(dot_dir)
|
|
29
31
|
code_map = self.extraction_service.load(architecture_root, language)
|