archi-cli 0.1.0__tar.gz → 0.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.
- archi_cli-0.2.0/PKG-INFO +167 -0
- archi_cli-0.2.0/README.md +145 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/pyproject.toml +23 -6
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/cli.py +83 -13
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/discovery.py +29 -16
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/engine.py +2 -2
- archi_cli-0.2.0/tools/archi_tool/links.py +109 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/model.py +98 -5
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/render.py +36 -2
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/render_html.py +39 -13
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/render_slides.py +24 -13
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/validate.py +59 -2
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/views.py +1 -2
- archi_cli-0.1.0/PKG-INFO +0 -214
- archi_cli-0.1.0/README.md +0 -192
- archi_cli-0.1.0/views/README.md +0 -10
- {archi_cli-0.1.0 → archi_cli-0.2.0}/.gitignore +0 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/LICENSE +0 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/NOTICE +0 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/__init__.py +0 -0
- {archi_cli-0.1.0 → archi_cli-0.2.0}/tools/archi_tool/normalize.py +0 -0
archi_cli-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: archi-cli
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: ar·cli·mate — de CLI in je ArchiMate: deterministisch native .archimate-modellen inspecteren, muteren, valideren en renderen
|
|
5
|
+
Project-URL: Homepage, https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting
|
|
6
|
+
Project-URL: Repository, https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting
|
|
7
|
+
Project-URL: Issues, https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/issues
|
|
8
|
+
Author: Bureau Architectuur Digitale Overheid
|
|
9
|
+
License-Expression: EUPL-1.2
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: archi,archimate,cli,enterprise-architecture,modeling
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Documentation
|
|
18
|
+
Requires-Python: >=3.12
|
|
19
|
+
Requires-Dist: lxml>=6.1.3
|
|
20
|
+
Requires-Dist: typer>=0.26
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# AI-assisted architecting
|
|
24
|
+
|
|
25
|
+
[](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/actions/workflows/ci.yml)
|
|
26
|
+
[](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/blob/main/LICENSE)
|
|
27
|
+
[](https://pypi.org/project/archi-cli/)
|
|
28
|
+
|
|
29
|
+
Een manier van architecteren waarbij een ArchiMate-model in het native
|
|
30
|
+
Archi-formaat de bron van waarheid is, en je het met AI-assistentie bijhoudt in
|
|
31
|
+
plaats van met de hand door de GUI te klikken. Deze repo levert de twee dingen
|
|
32
|
+
die daarvoor nodig zijn:
|
|
33
|
+
|
|
34
|
+
- **`archi-cli`**: een deterministische command line tool die een
|
|
35
|
+
`.archimate`-model inspecteert, muteert, valideert en rendert, en weigert een
|
|
36
|
+
kapot model op te slaan. Op PyPI, werkt op elk model.
|
|
37
|
+
- **De skills** (`archi-model`, `archi-view`, `archi-slides`): de werkwijze
|
|
38
|
+
die een AI-sessie zoals Claude Code volgt om via de CLI aan het model te
|
|
39
|
+
werken. Te installeren als Claude Code plugin.
|
|
40
|
+
|
|
41
|
+
Het model zelf hoort niet hier: dat woont in een eigen repo en gebruikt
|
|
42
|
+
`archi-cli` als tool. Het ADO-model waarvoor dit ontstond is daar het
|
|
43
|
+
voorbeeld.
|
|
44
|
+
|
|
45
|
+
## De CLI
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv tool install archi-cli
|
|
49
|
+
archi --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Dat zet het commando `archi` op je `$PATH`. Het model wordt gevonden via
|
|
53
|
+
`--model <pad>` (vóór het subcommando), via een `archi.toml` in de map
|
|
54
|
+
(`[tool.archi] model = "..."`), of als het enige `.archimate`-bestand in de
|
|
55
|
+
werkmap.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
archi stats # tellingen per type
|
|
59
|
+
archi list --type Capability # elementen, gefilterd
|
|
60
|
+
archi show "<elementnaam of id>" # één element met relaties
|
|
61
|
+
archi tree # folderstructuur
|
|
62
|
+
|
|
63
|
+
archi add-element --type Capability --name "..." --documentation "..."
|
|
64
|
+
archi add-relation --type Aggregation --source "..." --target "..." --name "bevat"
|
|
65
|
+
archi set-property <ref> "<key>=<waarde>" # property zetten of bijwerken
|
|
66
|
+
archi remove-property <ref> "<key>" # property verwijderen
|
|
67
|
+
archi set-documentation <ref> "..." # Archi's documentatieveld
|
|
68
|
+
archi add-element ... --subfolder "Gebied X" # in een submap van de laag
|
|
69
|
+
archi move <ref> --subfolder "Gebied X" # naar een submap verplaatsen
|
|
70
|
+
archi rename <ref> "Nieuwe naam"
|
|
71
|
+
archi remove <ref> --cascade
|
|
72
|
+
|
|
73
|
+
archi add-view --name "..." --layout cluster --type Capability --relation Aggregation
|
|
74
|
+
archi slides --deck decks/<naam>.toml
|
|
75
|
+
|
|
76
|
+
archi validate # integriteitschecks
|
|
77
|
+
archi normalize # canonieke serialisatie via Archi
|
|
78
|
+
archi render # views naar Mermaid en HTML
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Voor `normalize` is de Archi-engine nodig; die haalt `archi` bij het eerste
|
|
82
|
+
gebruik zelf op (of expliciet met `archi setup`), dus je hoeft Archi niet apart
|
|
83
|
+
te installeren. `archi <commando> --help` toont de volledige set vlaggen;
|
|
84
|
+
shell-completion zet je aan met `archi --install-completion`.
|
|
85
|
+
|
|
86
|
+
Wat de tool bijzonder maakt:
|
|
87
|
+
|
|
88
|
+
- **Muteren met een vangnet.** Elke wijziging valideert het model in het
|
|
89
|
+
geheugen; bij een fout weigert de CLI op te slaan. Ids zijn onveranderlijk.
|
|
90
|
+
- **Canonieke serialisatie.** `normalize` laat de headless Archi-engine het
|
|
91
|
+
bestand herschrijven, zodat git-diffs klein blijven, of de wijziging nu van
|
|
92
|
+
de tool of van de Archi-GUI kwam.
|
|
93
|
+
- **Renderen.** Elke view wordt een Mermaid-diagram (rendert op GitHub) en een
|
|
94
|
+
HTML-pagina die de layout uit het model volgt, plus een presentatie per
|
|
95
|
+
deckdefinitie.
|
|
96
|
+
- **Doorklikken tussen views.** Met een links-bestand
|
|
97
|
+
(`[tool.archi] links = "…"` in `archi.toml`) worden elementen in de
|
|
98
|
+
HTML-views en slides klikbaar naar een andere view of een ander bestand,
|
|
99
|
+
bijvoorbeeld een SVG die een project met eigen scripts maakt. Zie
|
|
100
|
+
hieronder.
|
|
101
|
+
|
|
102
|
+
### Doorklikken met een links-bestand
|
|
103
|
+
|
|
104
|
+
Welk element waarheen doorklikt is een redactionele keuze, geen modelfeit;
|
|
105
|
+
het staat daarom in een apart TOML-bestand (zie ADR 0010):
|
|
106
|
+
|
|
107
|
+
```toml
|
|
108
|
+
# archi.toml
|
|
109
|
+
[tool.archi]
|
|
110
|
+
links = "tools/links.toml"
|
|
111
|
+
|
|
112
|
+
# tools/links.toml
|
|
113
|
+
["overzicht-van-de-gebieden"] # bestandsnaam van de bronplaat, zonder extensie
|
|
114
|
+
"Gebied A" = "gebied-a-in-detail.html" # elementnaam = pad, relatief aan de bronplaat
|
|
115
|
+
"Gebied B" = "../eigen-svg/gebied-b.svg"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Een sectie hoort bij de HTML-view met die bestandsnaam (de slug van de
|
|
119
|
+
viewnaam; bij views met botsende namen de naam mét id-suffix die `archi
|
|
120
|
+
render` kiest), of bij een bestand dat een project zelf genereert: dezelfde
|
|
121
|
+
sectievorm is dan bruikbaar voor eigen scripts, zodat één bestand over alle
|
|
122
|
+
doorkliks gaat. In slides worden relatieve paden automatisch gecorrigeerd
|
|
123
|
+
voor de submap. `archi validate` waarschuwt voor elementnamen die niet in de
|
|
124
|
+
view (of het model) staan, `archi render` voor doelen die niet bestaan en
|
|
125
|
+
voor secties die bij geen view of bestand horen.
|
|
126
|
+
|
|
127
|
+
## De skills als Claude Code plugin
|
|
128
|
+
|
|
129
|
+
Deze repo is tegelijk de plugin (`archi-tools`) en de marketplace die hem
|
|
130
|
+
aanbiedt (`archi-marketplace`). Zo installeer je de skills bij je eigen model:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
/plugin marketplace add BureauArchitectuurDigitaleOverheid/ai-assisted-architecting
|
|
134
|
+
/plugin install archi-tools@archi-marketplace
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
De plugin brengt de drie skills mee; het `archi`-commando zelf komt van
|
|
138
|
+
`uv tool install archi-cli`. Zie `adr/0007-claude-code-plugin.md`.
|
|
139
|
+
|
|
140
|
+
## Ontwikkelen aan de tool
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
just setup # venv, dependencies en de pre-commit hook
|
|
144
|
+
just test # de testsuite (draait op een fixture-model)
|
|
145
|
+
just demo # de CLI op het fixture-model
|
|
146
|
+
just build # distributies bouwen en de metadata controleren
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
De testsuite draait op `tests/fixtures/klein-model.archimate`, niet op een echt
|
|
150
|
+
model. Wijzigingen lopen via een branch en een PR; CI draait de suite op Ubuntu
|
|
151
|
+
en Windows. Publiceren naar PyPI gebeurt op een versie-tag, zie `CLAUDE.md` en
|
|
152
|
+
`adr/0006-publiceren-op-pypi.md`.
|
|
153
|
+
|
|
154
|
+
## Structuur
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
tools/archi_tool/ De archi-CLI (Python, lxml, Typer; beheerd met uv)
|
|
158
|
+
tests/ pytest-suite met een klein fixture-model
|
|
159
|
+
.claude/skills/ De skills (archi-model, archi-view, archi-slides)
|
|
160
|
+
.claude-plugin/ Plugin- en marketplace-manifest
|
|
161
|
+
adr/ Architectuurbeslissingen over de tool
|
|
162
|
+
justfile Vaste taken
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Licentie
|
|
166
|
+
|
|
167
|
+
[EUPL-1.2](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/blob/main/LICENSE).
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# AI-assisted architecting
|
|
2
|
+
|
|
3
|
+
[](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/actions/workflows/ci.yml)
|
|
4
|
+
[](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/blob/main/LICENSE)
|
|
5
|
+
[](https://pypi.org/project/archi-cli/)
|
|
6
|
+
|
|
7
|
+
Een manier van architecteren waarbij een ArchiMate-model in het native
|
|
8
|
+
Archi-formaat de bron van waarheid is, en je het met AI-assistentie bijhoudt in
|
|
9
|
+
plaats van met de hand door de GUI te klikken. Deze repo levert de twee dingen
|
|
10
|
+
die daarvoor nodig zijn:
|
|
11
|
+
|
|
12
|
+
- **`archi-cli`**: een deterministische command line tool die een
|
|
13
|
+
`.archimate`-model inspecteert, muteert, valideert en rendert, en weigert een
|
|
14
|
+
kapot model op te slaan. Op PyPI, werkt op elk model.
|
|
15
|
+
- **De skills** (`archi-model`, `archi-view`, `archi-slides`): de werkwijze
|
|
16
|
+
die een AI-sessie zoals Claude Code volgt om via de CLI aan het model te
|
|
17
|
+
werken. Te installeren als Claude Code plugin.
|
|
18
|
+
|
|
19
|
+
Het model zelf hoort niet hier: dat woont in een eigen repo en gebruikt
|
|
20
|
+
`archi-cli` als tool. Het ADO-model waarvoor dit ontstond is daar het
|
|
21
|
+
voorbeeld.
|
|
22
|
+
|
|
23
|
+
## De CLI
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
uv tool install archi-cli
|
|
27
|
+
archi --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Dat zet het commando `archi` op je `$PATH`. Het model wordt gevonden via
|
|
31
|
+
`--model <pad>` (vóór het subcommando), via een `archi.toml` in de map
|
|
32
|
+
(`[tool.archi] model = "..."`), of als het enige `.archimate`-bestand in de
|
|
33
|
+
werkmap.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
archi stats # tellingen per type
|
|
37
|
+
archi list --type Capability # elementen, gefilterd
|
|
38
|
+
archi show "<elementnaam of id>" # één element met relaties
|
|
39
|
+
archi tree # folderstructuur
|
|
40
|
+
|
|
41
|
+
archi add-element --type Capability --name "..." --documentation "..."
|
|
42
|
+
archi add-relation --type Aggregation --source "..." --target "..." --name "bevat"
|
|
43
|
+
archi set-property <ref> "<key>=<waarde>" # property zetten of bijwerken
|
|
44
|
+
archi remove-property <ref> "<key>" # property verwijderen
|
|
45
|
+
archi set-documentation <ref> "..." # Archi's documentatieveld
|
|
46
|
+
archi add-element ... --subfolder "Gebied X" # in een submap van de laag
|
|
47
|
+
archi move <ref> --subfolder "Gebied X" # naar een submap verplaatsen
|
|
48
|
+
archi rename <ref> "Nieuwe naam"
|
|
49
|
+
archi remove <ref> --cascade
|
|
50
|
+
|
|
51
|
+
archi add-view --name "..." --layout cluster --type Capability --relation Aggregation
|
|
52
|
+
archi slides --deck decks/<naam>.toml
|
|
53
|
+
|
|
54
|
+
archi validate # integriteitschecks
|
|
55
|
+
archi normalize # canonieke serialisatie via Archi
|
|
56
|
+
archi render # views naar Mermaid en HTML
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Voor `normalize` is de Archi-engine nodig; die haalt `archi` bij het eerste
|
|
60
|
+
gebruik zelf op (of expliciet met `archi setup`), dus je hoeft Archi niet apart
|
|
61
|
+
te installeren. `archi <commando> --help` toont de volledige set vlaggen;
|
|
62
|
+
shell-completion zet je aan met `archi --install-completion`.
|
|
63
|
+
|
|
64
|
+
Wat de tool bijzonder maakt:
|
|
65
|
+
|
|
66
|
+
- **Muteren met een vangnet.** Elke wijziging valideert het model in het
|
|
67
|
+
geheugen; bij een fout weigert de CLI op te slaan. Ids zijn onveranderlijk.
|
|
68
|
+
- **Canonieke serialisatie.** `normalize` laat de headless Archi-engine het
|
|
69
|
+
bestand herschrijven, zodat git-diffs klein blijven, of de wijziging nu van
|
|
70
|
+
de tool of van de Archi-GUI kwam.
|
|
71
|
+
- **Renderen.** Elke view wordt een Mermaid-diagram (rendert op GitHub) en een
|
|
72
|
+
HTML-pagina die de layout uit het model volgt, plus een presentatie per
|
|
73
|
+
deckdefinitie.
|
|
74
|
+
- **Doorklikken tussen views.** Met een links-bestand
|
|
75
|
+
(`[tool.archi] links = "…"` in `archi.toml`) worden elementen in de
|
|
76
|
+
HTML-views en slides klikbaar naar een andere view of een ander bestand,
|
|
77
|
+
bijvoorbeeld een SVG die een project met eigen scripts maakt. Zie
|
|
78
|
+
hieronder.
|
|
79
|
+
|
|
80
|
+
### Doorklikken met een links-bestand
|
|
81
|
+
|
|
82
|
+
Welk element waarheen doorklikt is een redactionele keuze, geen modelfeit;
|
|
83
|
+
het staat daarom in een apart TOML-bestand (zie ADR 0010):
|
|
84
|
+
|
|
85
|
+
```toml
|
|
86
|
+
# archi.toml
|
|
87
|
+
[tool.archi]
|
|
88
|
+
links = "tools/links.toml"
|
|
89
|
+
|
|
90
|
+
# tools/links.toml
|
|
91
|
+
["overzicht-van-de-gebieden"] # bestandsnaam van de bronplaat, zonder extensie
|
|
92
|
+
"Gebied A" = "gebied-a-in-detail.html" # elementnaam = pad, relatief aan de bronplaat
|
|
93
|
+
"Gebied B" = "../eigen-svg/gebied-b.svg"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Een sectie hoort bij de HTML-view met die bestandsnaam (de slug van de
|
|
97
|
+
viewnaam; bij views met botsende namen de naam mét id-suffix die `archi
|
|
98
|
+
render` kiest), of bij een bestand dat een project zelf genereert: dezelfde
|
|
99
|
+
sectievorm is dan bruikbaar voor eigen scripts, zodat één bestand over alle
|
|
100
|
+
doorkliks gaat. In slides worden relatieve paden automatisch gecorrigeerd
|
|
101
|
+
voor de submap. `archi validate` waarschuwt voor elementnamen die niet in de
|
|
102
|
+
view (of het model) staan, `archi render` voor doelen die niet bestaan en
|
|
103
|
+
voor secties die bij geen view of bestand horen.
|
|
104
|
+
|
|
105
|
+
## De skills als Claude Code plugin
|
|
106
|
+
|
|
107
|
+
Deze repo is tegelijk de plugin (`archi-tools`) en de marketplace die hem
|
|
108
|
+
aanbiedt (`archi-marketplace`). Zo installeer je de skills bij je eigen model:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
/plugin marketplace add BureauArchitectuurDigitaleOverheid/ai-assisted-architecting
|
|
112
|
+
/plugin install archi-tools@archi-marketplace
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
De plugin brengt de drie skills mee; het `archi`-commando zelf komt van
|
|
116
|
+
`uv tool install archi-cli`. Zie `adr/0007-claude-code-plugin.md`.
|
|
117
|
+
|
|
118
|
+
## Ontwikkelen aan de tool
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
just setup # venv, dependencies en de pre-commit hook
|
|
122
|
+
just test # de testsuite (draait op een fixture-model)
|
|
123
|
+
just demo # de CLI op het fixture-model
|
|
124
|
+
just build # distributies bouwen en de metadata controleren
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
De testsuite draait op `tests/fixtures/klein-model.archimate`, niet op een echt
|
|
128
|
+
model. Wijzigingen lopen via een branch en een PR; CI draait de suite op Ubuntu
|
|
129
|
+
en Windows. Publiceren naar PyPI gebeurt op een versie-tag, zie `CLAUDE.md` en
|
|
130
|
+
`adr/0006-publiceren-op-pypi.md`.
|
|
131
|
+
|
|
132
|
+
## Structuur
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
tools/archi_tool/ De archi-CLI (Python, lxml, Typer; beheerd met uv)
|
|
136
|
+
tests/ pytest-suite met een klein fixture-model
|
|
137
|
+
.claude/skills/ De skills (archi-model, archi-view, archi-slides)
|
|
138
|
+
.claude-plugin/ Plugin- en marketplace-manifest
|
|
139
|
+
adr/ Architectuurbeslissingen over de tool
|
|
140
|
+
justfile Vaste taken
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Licentie
|
|
144
|
+
|
|
145
|
+
[EUPL-1.2](https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting/blob/main/LICENSE).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "archi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0"
|
|
4
4
|
description = "ar·cli·mate — de CLI in je ArchiMate: deterministisch native .archimate-modellen inspecteren, muteren, valideren en renderen"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "EUPL-1.2"
|
|
@@ -18,7 +18,8 @@ classifiers = [
|
|
|
18
18
|
"Programming Language :: Python :: 3.12",
|
|
19
19
|
"Programming Language :: Python :: 3.13",
|
|
20
20
|
]
|
|
21
|
-
|
|
21
|
+
# lxml 6.1.3 stops resolving external parameter entities by default
|
|
22
|
+
dependencies = ["lxml>=6.1.3", "typer>=0.26"]
|
|
22
23
|
|
|
23
24
|
[project.urls]
|
|
24
25
|
Homepage = "https://github.com/BureauArchitectuurDigitaleOverheid/ai-assisted-architecting"
|
|
@@ -38,12 +39,28 @@ build-backend = "hatchling.build"
|
|
|
38
39
|
packages = ["tools/archi_tool"]
|
|
39
40
|
|
|
40
41
|
[tool.hatch.build.targets.sdist]
|
|
42
|
+
# only these paths; the leading ./ anchors README.md so views/README.md and
|
|
43
|
+
# other nested READMEs don't get pulled in by a bare glob
|
|
41
44
|
include = [
|
|
42
|
-
"tools/archi_tool",
|
|
43
|
-
"README.md",
|
|
44
|
-
"LICENSE",
|
|
45
|
-
"NOTICE",
|
|
45
|
+
"/tools/archi_tool",
|
|
46
|
+
"/README.md",
|
|
47
|
+
"/LICENSE",
|
|
48
|
+
"/NOTICE",
|
|
49
|
+
"/pyproject.toml",
|
|
46
50
|
]
|
|
47
51
|
|
|
48
52
|
[dependency-groups]
|
|
49
53
|
dev = ["pytest>=8", "pre-commit>=4"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff]
|
|
56
|
+
target-version = "py312"
|
|
57
|
+
|
|
58
|
+
[tool.ruff.lint]
|
|
59
|
+
# Bugs and dead code first. Import sorting and the formatter follow in a
|
|
60
|
+
# separate PR once the open feature branches have landed, so this one does
|
|
61
|
+
# not cause merge conflicts in every file.
|
|
62
|
+
select = ["E", "F", "W", "B"]
|
|
63
|
+
ignore = [
|
|
64
|
+
"E501", # line length is left to the formatter
|
|
65
|
+
"B008", # Typer declares options as call defaults by design
|
|
66
|
+
]
|
|
@@ -16,10 +16,11 @@ from typing import Optional
|
|
|
16
16
|
import typer
|
|
17
17
|
from lxml import etree
|
|
18
18
|
|
|
19
|
-
from .discovery import discover_conventions, discover_model
|
|
19
|
+
from .discovery import discover_conventions, discover_links, discover_model
|
|
20
|
+
from .links import check_link_files, load_links
|
|
20
21
|
from .model import ArchiModel, ModelError, is_element, xsi_type
|
|
21
22
|
from .normalize import normalize
|
|
22
|
-
from .render import render_all, write_if_changed
|
|
23
|
+
from .render import render_all, view_stems, write_if_changed
|
|
23
24
|
from .render_html import render_all_html
|
|
24
25
|
from .render_slides import load_deck, render_all_slides, render_deck_html
|
|
25
26
|
from .validate import validate
|
|
@@ -42,7 +43,15 @@ def report_validation(model, model_path) -> bool:
|
|
|
42
43
|
# validating a model from another project uses that project's conventions
|
|
43
44
|
allowed_keys, _ = discover_conventions(
|
|
44
45
|
model_path, start=Path(model_path).resolve().parent)
|
|
45
|
-
|
|
46
|
+
# validation runs after every mutation: a broken links file must not
|
|
47
|
+
# block modelling (ADR 0010), so here it degrades to a warning; render
|
|
48
|
+
# and slides, which actually use the links, still fail on it
|
|
49
|
+
try:
|
|
50
|
+
links = load_links(discover_links(model_path))
|
|
51
|
+
except ModelError as exc:
|
|
52
|
+
print(f"WAARSCHUWING: links-bestand genegeerd: {exc}")
|
|
53
|
+
links = {}
|
|
54
|
+
errors, warnings = validate(model, allowed_keys=allowed_keys, links=links)
|
|
46
55
|
for warning in warnings:
|
|
47
56
|
print(f"WAARSCHUWING: {warning}")
|
|
48
57
|
for error in errors:
|
|
@@ -144,7 +153,9 @@ def cmd_validate(model, args):
|
|
|
144
153
|
def cmd_add_element(model, args):
|
|
145
154
|
el = model.add_element(args.type, args.name, folder_type=args.folder,
|
|
146
155
|
properties=parse_properties(args.property),
|
|
147
|
-
documentation=args.documentation
|
|
156
|
+
documentation=args.documentation,
|
|
157
|
+
subfolder=args.subfolder,
|
|
158
|
+
create_subfolder=args.create_subfolder)
|
|
148
159
|
status = save_validated(model, args)
|
|
149
160
|
if status == 0:
|
|
150
161
|
print(f"Toegevoegd: {describe(model, el)}")
|
|
@@ -153,7 +164,8 @@ def cmd_add_element(model, args):
|
|
|
153
164
|
|
|
154
165
|
def cmd_add_relation(model, args):
|
|
155
166
|
rel = model.add_relation(args.type, args.source, args.target,
|
|
156
|
-
name=args.name
|
|
167
|
+
name=args.name, subfolder=args.subfolder,
|
|
168
|
+
create_subfolder=args.create_subfolder)
|
|
157
169
|
status = save_validated(model, args)
|
|
158
170
|
if status == 0:
|
|
159
171
|
print(f"Toegevoegd: {rel.get('id')} [{xsi_type(rel)}] "
|
|
@@ -172,6 +184,23 @@ def cmd_set_property(model, args):
|
|
|
172
184
|
return status
|
|
173
185
|
|
|
174
186
|
|
|
187
|
+
def cmd_move(model, args):
|
|
188
|
+
target = model.move(args.ref, args.subfolder,
|
|
189
|
+
create_subfolder=args.create_subfolder)
|
|
190
|
+
status = save_validated(model, args)
|
|
191
|
+
if status == 0:
|
|
192
|
+
print(f"Verplaatst: '{args.ref}' naar folder '{target.get('name')}'")
|
|
193
|
+
return status
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def cmd_remove_property(model, args):
|
|
197
|
+
model.remove_property(args.ref, args.key)
|
|
198
|
+
status = save_validated(model, args)
|
|
199
|
+
if status == 0:
|
|
200
|
+
print(f"Property verwijderd van '{args.ref}': {args.key}")
|
|
201
|
+
return status
|
|
202
|
+
|
|
203
|
+
|
|
175
204
|
def cmd_rename(model, args):
|
|
176
205
|
model.rename(args.ref, args.name)
|
|
177
206
|
status = save_validated(model, args)
|
|
@@ -244,10 +273,12 @@ def cmd_setup(model, args):
|
|
|
244
273
|
|
|
245
274
|
|
|
246
275
|
def cmd_render(model, args):
|
|
276
|
+
links = load_links(discover_links(args.model))
|
|
247
277
|
written, removed = render_all(model, args.out)
|
|
248
|
-
|
|
278
|
+
html_dir = Path(args.out) / "html"
|
|
279
|
+
html_written, html_removed = render_all_html(model, html_dir, links=links)
|
|
249
280
|
deck_written, deck_removed = render_all_slides(
|
|
250
|
-
model, Path(args.decks),
|
|
281
|
+
model, Path(args.decks), html_dir / "slides", links=links)
|
|
251
282
|
for path in written + html_written + deck_written:
|
|
252
283
|
print(f"Geschreven: {path}")
|
|
253
284
|
for path in removed + html_removed:
|
|
@@ -257,22 +288,27 @@ def cmd_render(model, args):
|
|
|
257
288
|
if not (written or removed or html_written or html_removed
|
|
258
289
|
or deck_written or deck_removed):
|
|
259
290
|
print("Views zijn al actueel.")
|
|
291
|
+
view_files = set(view_stems(model).values())
|
|
292
|
+
for warning in check_link_files(links, args.out, html_dir, view_files):
|
|
293
|
+
print(f"WAARSCHUWING: {warning}")
|
|
260
294
|
return 0
|
|
261
295
|
|
|
262
296
|
|
|
263
297
|
def cmd_slides(model, args):
|
|
298
|
+
links = load_links(discover_links(args.model))
|
|
264
299
|
if args.deck:
|
|
265
300
|
written = []
|
|
266
301
|
for deck_path in args.deck:
|
|
267
302
|
deck = load_deck(Path(deck_path), model)
|
|
268
303
|
path = Path(args.out) / f"{deck['slug']}.html"
|
|
269
304
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
270
|
-
if write_if_changed(path, render_deck_html(model, deck
|
|
305
|
+
if write_if_changed(path, render_deck_html(model, deck,
|
|
306
|
+
links=links)):
|
|
271
307
|
written.append(path)
|
|
272
308
|
removed = []
|
|
273
309
|
else:
|
|
274
310
|
written, removed = render_all_slides(
|
|
275
|
-
model, Path(args.decks), Path(args.out))
|
|
311
|
+
model, Path(args.decks), Path(args.out), links=links)
|
|
276
312
|
for path in written:
|
|
277
313
|
print(f"Geschreven: {path}")
|
|
278
314
|
for path in removed:
|
|
@@ -330,10 +366,10 @@ def _run(command_fn, *, load_model=True, need_model=True, **fields) -> None:
|
|
|
330
366
|
status = command_fn(model, args)
|
|
331
367
|
except ModelError as exc:
|
|
332
368
|
typer.echo(f"FOUT: {exc}", err=True)
|
|
333
|
-
raise typer.Exit(1)
|
|
369
|
+
raise typer.Exit(1) from None
|
|
334
370
|
except (etree.XMLSyntaxError, OSError) as exc:
|
|
335
371
|
typer.echo(f"FOUT: kan {args.model} niet lezen: {exc}", err=True)
|
|
336
|
-
raise typer.Exit(1)
|
|
372
|
+
raise typer.Exit(1) from None
|
|
337
373
|
raise typer.Exit(status)
|
|
338
374
|
|
|
339
375
|
|
|
@@ -395,9 +431,15 @@ def add_element(
|
|
|
395
431
|
property: Optional[list[str]] = typer.Option(
|
|
396
432
|
None, "--property", help="key=value (herhaalbaar)"),
|
|
397
433
|
documentation: Optional[str] = typer.Option(None),
|
|
434
|
+
subfolder: Optional[str] = typer.Option(
|
|
435
|
+
None, help="submap binnen de laagfolder, geneste mappen met '/'"),
|
|
436
|
+
create_subfolder: bool = typer.Option(
|
|
437
|
+
False, "--create-subfolder",
|
|
438
|
+
help="ontbrekende submap aanmaken (anders: fout)"),
|
|
398
439
|
):
|
|
399
440
|
_run(cmd_add_element, type=type, name=name, folder=folder,
|
|
400
|
-
property=property, documentation=documentation
|
|
441
|
+
property=property, documentation=documentation,
|
|
442
|
+
subfolder=subfolder, create_subfolder=create_subfolder)
|
|
401
443
|
|
|
402
444
|
|
|
403
445
|
@app.command("add-relation", help="relatie toevoegen")
|
|
@@ -407,9 +449,29 @@ def add_relation(
|
|
|
407
449
|
source: str = typer.Option(..., help="id of unieke naam"),
|
|
408
450
|
target: str = typer.Option(..., help="id of unieke naam"),
|
|
409
451
|
name: Optional[str] = typer.Option(None, help="NL-label op de relatie"),
|
|
452
|
+
subfolder: Optional[str] = typer.Option(
|
|
453
|
+
None, help="submap binnen Relations, geneste mappen met '/'"),
|
|
454
|
+
create_subfolder: bool = typer.Option(
|
|
455
|
+
False, "--create-subfolder",
|
|
456
|
+
help="ontbrekende submap aanmaken (anders: fout)"),
|
|
410
457
|
):
|
|
411
458
|
_run(cmd_add_relation, type=type, source=source,
|
|
412
|
-
target=target, name=name
|
|
459
|
+
target=target, name=name, subfolder=subfolder,
|
|
460
|
+
create_subfolder=create_subfolder)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@app.command(help="element, relatie of view naar een submap verplaatsen")
|
|
464
|
+
def move(
|
|
465
|
+
ref: str = typer.Argument(help="id of (unieke) naam"),
|
|
466
|
+
subfolder: str = typer.Option(
|
|
467
|
+
..., help="submap binnen de huidige laagfolder, geneste mappen met "
|
|
468
|
+
"'/'; leeg ('') = terug naar de laagfolder zelf"),
|
|
469
|
+
create_subfolder: bool = typer.Option(
|
|
470
|
+
False, "--create-subfolder",
|
|
471
|
+
help="ontbrekende submap aanmaken (anders: fout)"),
|
|
472
|
+
):
|
|
473
|
+
_run(cmd_move, ref=ref, subfolder=subfolder,
|
|
474
|
+
create_subfolder=create_subfolder)
|
|
413
475
|
|
|
414
476
|
|
|
415
477
|
@app.command("set-property", help="property zetten of bijwerken")
|
|
@@ -420,6 +482,14 @@ def set_property(
|
|
|
420
482
|
_run(cmd_set_property, ref=ref, pair=pair)
|
|
421
483
|
|
|
422
484
|
|
|
485
|
+
@app.command("remove-property", help="property verwijderen")
|
|
486
|
+
def remove_property(
|
|
487
|
+
ref: str = typer.Argument(help="id of (unieke) naam"),
|
|
488
|
+
key: str = typer.Argument(help="property-key"),
|
|
489
|
+
):
|
|
490
|
+
_run(cmd_remove_property, ref=ref, key=key)
|
|
491
|
+
|
|
492
|
+
|
|
423
493
|
@app.command(help="element of relatie hernoemen")
|
|
424
494
|
def rename(
|
|
425
495
|
ref: str = typer.Argument(help="id of (unieke) naam"),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
"""Locate the model file and the conventions list without assuming
|
|
1
|
+
"""Locate the model file and the conventions list without assuming a layout.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
An earlier version hardcoded a fixed model path and found the conventions doc
|
|
4
|
+
at a fixed location. Discovery makes ``archi`` work on any model, in any
|
|
5
|
+
layout: a project points at its model and conventions through an ``archi.toml``
|
|
6
|
+
(or ``pyproject.toml``), or the tool falls back to sensible defaults.
|
|
7
7
|
|
|
8
8
|
Resolution order for the model:
|
|
9
9
|
1. an explicit ``--model`` path (handled by the caller)
|
|
@@ -27,19 +27,14 @@ from .model import ModelError
|
|
|
27
27
|
CONFIG_NAMES = ("archi.toml", "pyproject.toml")
|
|
28
28
|
CONVENTION_NEIGHBOURS = ("conventies.md", "conventions.md")
|
|
29
29
|
|
|
30
|
-
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
30
|
+
# Generic keys that ship with the tool as a fallback allowlist. A project
|
|
31
|
+
# overrides this by pointing at its own conventions doc (with a Property-keys
|
|
32
|
+
# section); without one, these keep the property-key check useful instead of
|
|
33
|
+
# silently disabling it. Descriptions belong in Archi's documentation field,
|
|
34
|
+
# not in a property, so "Omschrijving"/"Toelichting" are deliberately absent
|
|
35
|
+
# (ADR 0009).
|
|
34
36
|
DEFAULT_PROPERTY_KEYS = {
|
|
35
|
-
"Omschrijving",
|
|
36
|
-
"Toelichting",
|
|
37
37
|
"Bron",
|
|
38
|
-
"Capability-niveau",
|
|
39
|
-
"Niveau (herkomst)",
|
|
40
|
-
"Driver-categorie",
|
|
41
|
-
"constraint-type",
|
|
42
|
-
"Artefact-type",
|
|
43
38
|
}
|
|
44
39
|
|
|
45
40
|
|
|
@@ -101,6 +96,24 @@ def discover_model(explicit=None, start=None):
|
|
|
101
96
|
"--model <pad> of leg het vast in archi.toml.")
|
|
102
97
|
|
|
103
98
|
|
|
99
|
+
def discover_links(model_path, start=None):
|
|
100
|
+
"""Return the path of the links file, or None when none is configured.
|
|
101
|
+
|
|
102
|
+
Only ``[tool.archi] links`` in a config file counts (there is no
|
|
103
|
+
filesystem fallback: click-through is opt-in). A configured path that
|
|
104
|
+
does not exist is a config error.
|
|
105
|
+
"""
|
|
106
|
+
start = Path(start or Path(model_path).resolve().parent)
|
|
107
|
+
table, config_path = _read_archi_config(start)
|
|
108
|
+
if "links" not in table:
|
|
109
|
+
return None
|
|
110
|
+
path = (config_path.parent / table["links"]).resolve()
|
|
111
|
+
if not path.exists():
|
|
112
|
+
raise ModelError(
|
|
113
|
+
f"Linkspad uit {config_path.name} bestaat niet: {path}")
|
|
114
|
+
return path
|
|
115
|
+
|
|
116
|
+
|
|
104
117
|
def discover_conventions(model_path, start=None):
|
|
105
118
|
"""Return (allowed_property_keys, source_label).
|
|
106
119
|
|
|
@@ -153,7 +153,7 @@ def _safe_extract_tar(tar, target):
|
|
|
153
153
|
try:
|
|
154
154
|
tar.extractall(target, filter="data")
|
|
155
155
|
except tarfile.FilterError as exc:
|
|
156
|
-
raise ModelError(f"Onveilig pad in archief: {exc}")
|
|
156
|
+
raise ModelError(f"Onveilig pad in archief: {exc}") from exc
|
|
157
157
|
|
|
158
158
|
|
|
159
159
|
def _safe_extract_zip(zf, target):
|
|
@@ -259,7 +259,7 @@ def download_engine(*, quiet=False) -> Path:
|
|
|
259
259
|
os.replace(staging, version_dir)
|
|
260
260
|
except OSError as exc:
|
|
261
261
|
raise ModelError(
|
|
262
|
-
f"Kon de Archi-engine niet ophalen van {url}: {exc}")
|
|
262
|
+
f"Kon de Archi-engine niet ophalen van {url}: {exc}") from exc
|
|
263
263
|
finally:
|
|
264
264
|
shutil.rmtree(staging, ignore_errors=True)
|
|
265
265
|
|