slartiarch 0.1.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.
- slartiarch-0.1.0/.adr-dir +1 -0
- slartiarch-0.1.0/.github/scripts/check_radon.sh +33 -0
- slartiarch-0.1.0/.github/workflows/ci.yml +51 -0
- slartiarch-0.1.0/.gitignore +14 -0
- slartiarch-0.1.0/.nvimrc +23 -0
- slartiarch-0.1.0/.pre-commit-config.yaml +17 -0
- slartiarch-0.1.0/AGENTS.md +92 -0
- slartiarch-0.1.0/Makefile +41 -0
- slartiarch-0.1.0/PKG-INFO +114 -0
- slartiarch-0.1.0/README.md +97 -0
- slartiarch-0.1.0/docs/adr/0001-python-project.md +31 -0
- slartiarch-0.1.0/docs/adr/0002-findings-are-the-interface.md +45 -0
- slartiarch-0.1.0/docs/adr/0003-python-interfaces-are-a-linkml-projection.md +54 -0
- slartiarch-0.1.0/docs/architecture.md +285 -0
- slartiarch-0.1.0/docs/diagrams/containers.mmd +44 -0
- slartiarch-0.1.0/docs/diagrams/index.mmd +19 -0
- slartiarch-0.1.0/docs/slarti/constraints.yaml +102 -0
- slartiarch-0.1.0/docs/slarti/data/invalid/D1.ttl +10 -0
- slartiarch-0.1.0/docs/slarti/data/invalid/D2.ttl +8 -0
- slartiarch-0.1.0/docs/slarti/data/invalid/D3.ttl +7 -0
- slartiarch-0.1.0/docs/slarti/data/valid/report.ttl +17 -0
- slartiarch-0.1.0/docs/slarti/likec4/slarti.c4 +129 -0
- slartiarch-0.1.0/docs/slarti/linkml/slarti.yaml +247 -0
- slartiarch-0.1.0/docs/slarti/shacl/invariants.ttl +36 -0
- slartiarch-0.1.0/docs/specs/slarti-project.md +373 -0
- slartiarch-0.1.0/package-lock.json +2112 -0
- slartiarch-0.1.0/package.json +13 -0
- slartiarch-0.1.0/pyproject.toml +95 -0
- slartiarch-0.1.0/slarti/__init__.py +1 -0
- slartiarch-0.1.0/slarti/checks/__init__.py +0 -0
- slartiarch-0.1.0/slarti/checks/delegated.py +47 -0
- slartiarch-0.1.0/slarti/checks/doc_checks.py +139 -0
- slartiarch-0.1.0/slarti/checks/ownership.py +125 -0
- slartiarch-0.1.0/slarti/checks/registry_checks.py +201 -0
- slartiarch-0.1.0/slarti/cli.py +167 -0
- slartiarch-0.1.0/slarti/config.py +118 -0
- slartiarch-0.1.0/slarti/docs.py +57 -0
- slartiarch-0.1.0/slarti/domain.py +286 -0
- slartiarch-0.1.0/slarti/env.py +130 -0
- slartiarch-0.1.0/slarti/findings.py +86 -0
- slartiarch-0.1.0/slarti/fixtures.py +124 -0
- slartiarch-0.1.0/slarti/generate.py +105 -0
- slartiarch-0.1.0/slarti/inject.py +53 -0
- slartiarch-0.1.0/slarti/models.py +145 -0
- slartiarch-0.1.0/slarti/proc.py +47 -0
- slartiarch-0.1.0/slarti/registry.py +99 -0
- slartiarch-0.1.0/slarti/report.py +348 -0
- slartiarch-0.1.0/slarti/resolvers.py +74 -0
- slartiarch-0.1.0/slarti/runner.py +48 -0
- slartiarch-0.1.0/slarti/scaffold.py +126 -0
- slartiarch-0.1.0/tests/__init__.py +0 -0
- slartiarch-0.1.0/tests/conftest.py +60 -0
- slartiarch-0.1.0/tests/test_cli.py +102 -0
- slartiarch-0.1.0/tests/test_config.py +63 -0
- slartiarch-0.1.0/tests/test_doc_checks.py +76 -0
- slartiarch-0.1.0/tests/test_dogfood.py +71 -0
- slartiarch-0.1.0/tests/test_domain.py +60 -0
- slartiarch-0.1.0/tests/test_env.py +53 -0
- slartiarch-0.1.0/tests/test_findings.py +46 -0
- slartiarch-0.1.0/tests/test_fixtures.py +58 -0
- slartiarch-0.1.0/tests/test_generate.py +61 -0
- slartiarch-0.1.0/tests/test_inject.py +51 -0
- slartiarch-0.1.0/tests/test_models.py +65 -0
- slartiarch-0.1.0/tests/test_ownership.py +54 -0
- slartiarch-0.1.0/tests/test_registry.py +119 -0
- slartiarch-0.1.0/tests/test_scaffold.py +46 -0
- slartiarch-0.1.0/uv.lock +2981 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
docs/adr
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# nounset: undefined variable outputs error message, and forces an exit
|
|
4
|
+
set -u
|
|
5
|
+
# errexit: abort script at first error
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "Radon Cyclomatic Complexity below grade A:"
|
|
9
|
+
PACKAGE="slarti"
|
|
10
|
+
CC_COMMAND="uv run radon cc -n B ${PACKAGE}"
|
|
11
|
+
CC=$($CC_COMMAND)
|
|
12
|
+
$CC_COMMAND
|
|
13
|
+
|
|
14
|
+
echo "Radon Maintainability Index below grade A:"
|
|
15
|
+
MI_COMMAND="uv run radon mi -n B ${PACKAGE}"
|
|
16
|
+
MI=$($MI_COMMAND)
|
|
17
|
+
$MI_COMMAND
|
|
18
|
+
|
|
19
|
+
echo "Lines of Code over 80 lines:"
|
|
20
|
+
L=$(uv run radon cc --json "${PACKAGE}" | jq -r 'to_entries[] |
|
|
21
|
+
.key as $file |
|
|
22
|
+
.value[] |
|
|
23
|
+
select(.type == "method" or .type == "function") |
|
|
24
|
+
(.endline - .lineno + 1) as $length |
|
|
25
|
+
select($length > 80) |
|
|
26
|
+
"\($file):\(.lineno) - \(.name) (\($length) lines)"
|
|
27
|
+
')
|
|
28
|
+
echo "$L"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if [[ ${#CC} -gt 0 || ${#MI} -gt 0 || ${#L} -gt 0 ]]; then
|
|
32
|
+
exit 1
|
|
33
|
+
fi
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
pull_request:
|
|
8
|
+
branches:
|
|
9
|
+
- main
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint-and-test:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- name: Checkout repository
|
|
18
|
+
uses: actions/checkout@v5
|
|
19
|
+
|
|
20
|
+
- name: Set up python
|
|
21
|
+
uses: actions/setup-python@v6
|
|
22
|
+
with:
|
|
23
|
+
python-version: "3.11"
|
|
24
|
+
|
|
25
|
+
- name: Set up node
|
|
26
|
+
uses: actions/setup-node@v6
|
|
27
|
+
with:
|
|
28
|
+
node-version: "20"
|
|
29
|
+
|
|
30
|
+
- name: Install the pinned LikeC4
|
|
31
|
+
run: npm ci
|
|
32
|
+
|
|
33
|
+
- name: Install uv
|
|
34
|
+
run: pip install uv
|
|
35
|
+
|
|
36
|
+
- name: Sync deps (dev)
|
|
37
|
+
run: uv sync --group dev
|
|
38
|
+
|
|
39
|
+
- name: CI
|
|
40
|
+
run: make ci
|
|
41
|
+
|
|
42
|
+
- name: Architecture (slarti dogfoods itself)
|
|
43
|
+
run: make arch
|
|
44
|
+
|
|
45
|
+
- name: Upload code coverage
|
|
46
|
+
if: always()
|
|
47
|
+
uses: actions/upload-artifact@v5
|
|
48
|
+
with:
|
|
49
|
+
name: coverage-html
|
|
50
|
+
path: htmlcov/
|
|
51
|
+
if-no-files-found: ignore
|
slartiarch-0.1.0/.nvimrc
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
" lua << EOF
|
|
2
|
+
" require('lazy-loader')()
|
|
3
|
+
" EOF
|
|
4
|
+
|
|
5
|
+
let g:gutentags_ctags_exclude += ['*/.venv/*']
|
|
6
|
+
let g:projectionist_heuristics = {
|
|
7
|
+
\ 'pyproject.toml': {
|
|
8
|
+
\ 'slarti/*.py': {
|
|
9
|
+
\ 'type': 'function',
|
|
10
|
+
\ 'alternate': [
|
|
11
|
+
\ 'tests/{dirname}test_{basename}.py',
|
|
12
|
+
\ 'tests/{dirname}/test_{basename}.py',
|
|
13
|
+
\ ]
|
|
14
|
+
\ },
|
|
15
|
+
\ 'tests/**/test_*.py': {
|
|
16
|
+
\ 'type': 'test',
|
|
17
|
+
\ 'alternate': [
|
|
18
|
+
\ 'slarti/{dirname}{basename}.py',
|
|
19
|
+
\ 'slarti/{dirname}/{basename}.py',
|
|
20
|
+
\ ]
|
|
21
|
+
\ },
|
|
22
|
+
\ },
|
|
23
|
+
\ }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.16.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff-check
|
|
6
|
+
- id: ruff-format
|
|
7
|
+
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
8
|
+
rev: v2.3.0
|
|
9
|
+
hooks:
|
|
10
|
+
- id: mypy
|
|
11
|
+
additional_dependencies: []
|
|
12
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
13
|
+
rev: v6.0.0
|
|
14
|
+
hooks:
|
|
15
|
+
- id: check-merge-conflict
|
|
16
|
+
- id: end-of-file-fixer
|
|
17
|
+
- id: trailing-whitespace
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Agent Guidelines
|
|
2
|
+
|
|
3
|
+
This file provides guidance when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
See this projects README.md for an overview of the project, and ADR documents in docs/adr/ for architectural decisions.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
**IMPORTANT**: Always use the Makefile commands for development tasks.
|
|
12
|
+
|
|
13
|
+
### Setup
|
|
14
|
+
```bash
|
|
15
|
+
make setup # Set up venv and sync dependencies with uv
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Testing
|
|
19
|
+
```bash
|
|
20
|
+
make test # Run all tests with pytest and coverage
|
|
21
|
+
uv run pytest tests/test_foo.py # Run specific test file
|
|
22
|
+
uv run pytest tests/test_foo.py::test_name # Run specific test
|
|
23
|
+
uv run pytest -v # Verbose output
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Code Quality
|
|
27
|
+
```bash
|
|
28
|
+
make lint # Check code with ruff
|
|
29
|
+
make fix # Auto-fix ruff issues
|
|
30
|
+
make type # Type check with mypy (strict mode)
|
|
31
|
+
make radon # Check cyclomatic complexity and maintainability
|
|
32
|
+
make ci # Run all CI checks (lint + type + test + radon)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**Before completing any feature**, run `make ci` to ensure all checks pass.
|
|
36
|
+
|
|
37
|
+
## Code Quality Requirements
|
|
38
|
+
|
|
39
|
+
Writing guidance:
|
|
40
|
+
|
|
41
|
+
- Only include a brief description of the thing in docstrings (no arguments or return types)
|
|
42
|
+
- Do not use docstrings at the beginning of files.
|
|
43
|
+
|
|
44
|
+
## Project Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
slarti/ # Main package source code
|
|
48
|
+
tests/ # Test files (mirror slarti/ structure)
|
|
49
|
+
model/ # The architecture model slarti checks itself against
|
|
50
|
+
docs/adr/ # Architecture Decision Records
|
|
51
|
+
docs/architecture.md # Generated in part by slarti itself
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## The architecture model (slarti dogfoods itself)
|
|
55
|
+
|
|
56
|
+
This repository is checked by its own tool. Before committing a change that
|
|
57
|
+
touches structure or domain entities, run:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
slartiarch check # the seams: OWN-*, REG-*, DOC-*
|
|
61
|
+
slartiarch docs # regenerate the diagrams and tables
|
|
62
|
+
slartiarch docs --check # the drift gate CI runs
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Files you may edit
|
|
66
|
+
|
|
67
|
+
- `model/arch/*.c4` — the LikeC4 model of slarti's own containers
|
|
68
|
+
- `model/schema/slarti.yaml` — the LinkML schema of slarti's domain entities
|
|
69
|
+
- `model/shapes/*.ttl` — hand-written SHACL
|
|
70
|
+
- `model/constraints.yaml` — the rule registry
|
|
71
|
+
- `model/data/valid/*`, `model/data/invalid/*` — fixtures, one per rule
|
|
72
|
+
- prose **outside** generated regions in `docs/architecture.md`
|
|
73
|
+
|
|
74
|
+
### Files you must never edit
|
|
75
|
+
|
|
76
|
+
- anything under `docs/diagrams/`
|
|
77
|
+
- anything between `<!-- slarti:begin ... -->` and `<!-- slarti:end ... -->`
|
|
78
|
+
|
|
79
|
+
Those regions are regenerated by `slartiarch docs`; hand edits are reported as
|
|
80
|
+
`DOC-1` / `DOC-2` and block the merge.
|
|
81
|
+
|
|
82
|
+
### Rules
|
|
83
|
+
|
|
84
|
+
- A new domain class needs an `owner` annotation naming a LikeC4 element, and
|
|
85
|
+
that element must list the class in its `owns` metadata (`OWN-1`..`OWN-5`).
|
|
86
|
+
- A new SHACL shape needs a constraint in `model/constraints.yaml` referencing
|
|
87
|
+
it, and a fixture under `model/data/invalid/` that makes it fire
|
|
88
|
+
(`REG-2`, `REG-6`, `REG-7`).
|
|
89
|
+
- A rule that cannot be mechanically enforced is written as `enforced_by: none`
|
|
90
|
+
with a `reason`. That is honest, not a failure.
|
|
91
|
+
- `slartiarch report --json` describes every rule, shape, owned class and check ID,
|
|
92
|
+
each check with its remedy.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
.PHONY: setup test lint type adr new coverage vulture fix radon treepeat ci arch docs domain
|
|
2
|
+
|
|
3
|
+
setup:
|
|
4
|
+
uv venv
|
|
5
|
+
uv sync --python .venv/bin/python
|
|
6
|
+
|
|
7
|
+
test:
|
|
8
|
+
uv run pytest
|
|
9
|
+
|
|
10
|
+
lint:
|
|
11
|
+
uv run ruff check .
|
|
12
|
+
|
|
13
|
+
vulture:
|
|
14
|
+
uv run vulture --min-confidence 55 slarti
|
|
15
|
+
|
|
16
|
+
fix:
|
|
17
|
+
uv run ruff check . --fix
|
|
18
|
+
|
|
19
|
+
type:
|
|
20
|
+
uv run mypy
|
|
21
|
+
|
|
22
|
+
radon:
|
|
23
|
+
uv run .github/scripts/check_radon.sh
|
|
24
|
+
|
|
25
|
+
treepeat:
|
|
26
|
+
uv run treepeat detect . -i '**/node_modules/**,**/.venv/**,**/build/**'
|
|
27
|
+
|
|
28
|
+
ci: test lint type radon treepeat vulture
|
|
29
|
+
|
|
30
|
+
# The Python interfaces are a LinkML projection, not hand-written (see README).
|
|
31
|
+
domain:
|
|
32
|
+
uv run gen-pydantic docs/slarti/linkml/slarti.yaml > slarti/domain.py
|
|
33
|
+
|
|
34
|
+
# slarti dogfoods itself (I13): the architecture document is generated by the tool.
|
|
35
|
+
arch:
|
|
36
|
+
uv run slartiarch doctor
|
|
37
|
+
uv run slartiarch check
|
|
38
|
+
uv run slartiarch docs --check
|
|
39
|
+
|
|
40
|
+
docs:
|
|
41
|
+
uv run slartiarch docs
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: slartiarch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A coordination CLI for validated architecture documentation
|
|
5
|
+
Author-email: Dane Summers <dsummersl@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Typing :: Typed
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: linkml-runtime>=1.11.1
|
|
11
|
+
Requires-Dist: pydantic>=2.11
|
|
12
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
13
|
+
Requires-Dist: rdflib>=7.6
|
|
14
|
+
Requires-Dist: tomli-w>=1.2
|
|
15
|
+
Requires-Dist: typer>=0.27
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
# slartiarch
|
|
19
|
+
|
|
20
|
+
<div align="center">
|
|
21
|
+
<img src="https://static.wikia.nocookie.net/hitchhikers/images/f/f9/Slartibartfast_comics.png/revision/latest?cb=20230703161559" width="200" alt="Slartibartfast">
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
Slartibartfast: the architect. From *The Hitchhiker's Guide to the Galaxy*, the curmudgeonly designer of Norway's fjords — and the namesake of this tool.
|
|
25
|
+
|
|
26
|
+
slartiarch is a coordination CLI that checks consistency between [LikeC4](https://likec4.dev) architecture
|
|
27
|
+
diagrams, [LinkML](https://linkml.io) schemas, and [SHACL](https://www.w3.org/TR/shacl/) shapes.
|
|
28
|
+
It also regenerates the diagrams in your architecture document so the prose never drifts.
|
|
29
|
+
|
|
30
|
+
Each tool is useful on its own with no slartiarch involved. slartiarch adds the seams — rules that span
|
|
31
|
+
two tools, and checks that the answers still agree.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
uv tool install slartiarch
|
|
37
|
+
npm install likec4
|
|
38
|
+
pip install linkml pyshacl
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
slartiarch init # scaffold docs/slarti/ and a config
|
|
45
|
+
slartiarch doctor # resolved paths and tool versions
|
|
46
|
+
slartiarch check # run all validations (CI command)
|
|
47
|
+
slartiarch check --json # structured output for automation
|
|
48
|
+
slartiarch docs # regenerate diagrams and tables into your docs
|
|
49
|
+
slartiarch docs --check # fail if committed output is stale
|
|
50
|
+
slartiarch report # every rule, shape, and check ID
|
|
51
|
+
slartiarch report --json # fully detailed, for agents
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Run `slartiarch` with no arguments for a full reference. `slartiarch report --json` describes every
|
|
55
|
+
rule, enforcer kind, check ID, and remedy.
|
|
56
|
+
|
|
57
|
+
## Layout
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
docs/
|
|
61
|
+
architecture.md prose with generated diagram and table regions
|
|
62
|
+
diagrams/ GENERATED — never hand-edited
|
|
63
|
+
slarti/
|
|
64
|
+
likec4/*.c4 LikeC4 architecture model
|
|
65
|
+
linkml/*.yaml LinkML domain schemas
|
|
66
|
+
shacl/*.ttl hand-written SHACL shapes
|
|
67
|
+
constraints.yaml rule registry
|
|
68
|
+
data/valid/* fixtures that must pass validation
|
|
69
|
+
data/invalid/* one fixture per SHACL rule; each must fail
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## How the tools work together
|
|
73
|
+
|
|
74
|
+
LikeC4 draws the system. LinkML defines the entities. SHACL validates instances. Each
|
|
75
|
+
works standalone:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
likec4 start docs/slarti/likec4 # live architecture browser
|
|
79
|
+
gen-pydantic docs/slarti/linkml/slarti.yaml # Python interfaces from schema
|
|
80
|
+
pyshacl -s docs/slarti/shacl/invariants.ttl ... # validate RDF against shapes
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
A constraint in the registry ties them together:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# docs/slarti/constraints.yaml
|
|
87
|
+
- id: D8
|
|
88
|
+
statement: A subtask belongs to the same list as its parent.
|
|
89
|
+
enforced_by:
|
|
90
|
+
kind: shacl_shape
|
|
91
|
+
ref: "todo:SubtaskSharesListWithParent"
|
|
92
|
+
fixture: docs/slarti/data/invalid/D8.yaml
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Running `slartiarch check` walks the seams: it verifies the shape exists, the fixture
|
|
96
|
+
fails (proving the shape fires), both halves of the ownership claim agree, and the
|
|
97
|
+
constraint table in your document still matches. `slartiarch docs` regenerates the Mermaid
|
|
98
|
+
diagrams and tables so the architecture document never goes stale.
|
|
99
|
+
|
|
100
|
+
## Dogfooding
|
|
101
|
+
|
|
102
|
+
This repository's [docs/architecture.md](docs/architecture.md) is generated by slartiarch from
|
|
103
|
+
`docs/slarti/`. CI runs `slartiarch check` and `slartiarch docs --check`.
|
|
104
|
+
|
|
105
|
+
## Dev
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
make setup # uv venv + sync
|
|
109
|
+
npm ci # pinned LikeC4
|
|
110
|
+
make domain # regenerate slarti/domain.py from the LinkML schema
|
|
111
|
+
make ci # tests, lint, types, complexity, duplication, dead code
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Architecture decisions live in `docs/adr/`.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# slartiarch
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
<img src="https://static.wikia.nocookie.net/hitchhikers/images/f/f9/Slartibartfast_comics.png/revision/latest?cb=20230703161559" width="200" alt="Slartibartfast">
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
Slartibartfast: the architect. From *The Hitchhiker's Guide to the Galaxy*, the curmudgeonly designer of Norway's fjords — and the namesake of this tool.
|
|
8
|
+
|
|
9
|
+
slartiarch is a coordination CLI that checks consistency between [LikeC4](https://likec4.dev) architecture
|
|
10
|
+
diagrams, [LinkML](https://linkml.io) schemas, and [SHACL](https://www.w3.org/TR/shacl/) shapes.
|
|
11
|
+
It also regenerates the diagrams in your architecture document so the prose never drifts.
|
|
12
|
+
|
|
13
|
+
Each tool is useful on its own with no slartiarch involved. slartiarch adds the seams — rules that span
|
|
14
|
+
two tools, and checks that the answers still agree.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv tool install slartiarch
|
|
20
|
+
npm install likec4
|
|
21
|
+
pip install linkml pyshacl
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Use
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
slartiarch init # scaffold docs/slarti/ and a config
|
|
28
|
+
slartiarch doctor # resolved paths and tool versions
|
|
29
|
+
slartiarch check # run all validations (CI command)
|
|
30
|
+
slartiarch check --json # structured output for automation
|
|
31
|
+
slartiarch docs # regenerate diagrams and tables into your docs
|
|
32
|
+
slartiarch docs --check # fail if committed output is stale
|
|
33
|
+
slartiarch report # every rule, shape, and check ID
|
|
34
|
+
slartiarch report --json # fully detailed, for agents
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run `slartiarch` with no arguments for a full reference. `slartiarch report --json` describes every
|
|
38
|
+
rule, enforcer kind, check ID, and remedy.
|
|
39
|
+
|
|
40
|
+
## Layout
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
docs/
|
|
44
|
+
architecture.md prose with generated diagram and table regions
|
|
45
|
+
diagrams/ GENERATED — never hand-edited
|
|
46
|
+
slarti/
|
|
47
|
+
likec4/*.c4 LikeC4 architecture model
|
|
48
|
+
linkml/*.yaml LinkML domain schemas
|
|
49
|
+
shacl/*.ttl hand-written SHACL shapes
|
|
50
|
+
constraints.yaml rule registry
|
|
51
|
+
data/valid/* fixtures that must pass validation
|
|
52
|
+
data/invalid/* one fixture per SHACL rule; each must fail
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## How the tools work together
|
|
56
|
+
|
|
57
|
+
LikeC4 draws the system. LinkML defines the entities. SHACL validates instances. Each
|
|
58
|
+
works standalone:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
likec4 start docs/slarti/likec4 # live architecture browser
|
|
62
|
+
gen-pydantic docs/slarti/linkml/slarti.yaml # Python interfaces from schema
|
|
63
|
+
pyshacl -s docs/slarti/shacl/invariants.ttl ... # validate RDF against shapes
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
A constraint in the registry ties them together:
|
|
67
|
+
|
|
68
|
+
```yaml
|
|
69
|
+
# docs/slarti/constraints.yaml
|
|
70
|
+
- id: D8
|
|
71
|
+
statement: A subtask belongs to the same list as its parent.
|
|
72
|
+
enforced_by:
|
|
73
|
+
kind: shacl_shape
|
|
74
|
+
ref: "todo:SubtaskSharesListWithParent"
|
|
75
|
+
fixture: docs/slarti/data/invalid/D8.yaml
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Running `slartiarch check` walks the seams: it verifies the shape exists, the fixture
|
|
79
|
+
fails (proving the shape fires), both halves of the ownership claim agree, and the
|
|
80
|
+
constraint table in your document still matches. `slartiarch docs` regenerates the Mermaid
|
|
81
|
+
diagrams and tables so the architecture document never goes stale.
|
|
82
|
+
|
|
83
|
+
## Dogfooding
|
|
84
|
+
|
|
85
|
+
This repository's [docs/architecture.md](docs/architecture.md) is generated by slartiarch from
|
|
86
|
+
`docs/slarti/`. CI runs `slartiarch check` and `slartiarch docs --check`.
|
|
87
|
+
|
|
88
|
+
## Dev
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
make setup # uv venv + sync
|
|
92
|
+
npm ci # pinned LikeC4
|
|
93
|
+
make domain # regenerate slarti/domain.py from the LinkML schema
|
|
94
|
+
make ci # tests, lint, types, complexity, duplication, dead code
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Architecture decisions live in `docs/adr/`.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 1. Python project
|
|
2
|
+
|
|
3
|
+
Date: 2025-09-05
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
A new project is being started that will be implemented in Python.
|
|
12
|
+
|
|
13
|
+
## Decision
|
|
14
|
+
|
|
15
|
+
Use a standard layout for the project with the following details:
|
|
16
|
+
- Use [uv](https://docs.astral.sh/uv/) as the package manager.
|
|
17
|
+
- Use [pytest](https://docs.pytest.org/en/stable/) as the test framework (with coverage).
|
|
18
|
+
- Use [adr-tools](https://github.com/npryce/adr-tools) to document architectural decisions.
|
|
19
|
+
|
|
20
|
+
Use the following layout for the python project:
|
|
21
|
+
|
|
22
|
+
```plaintext
|
|
23
|
+
project-root/
|
|
24
|
+
├── slarti/
|
|
25
|
+
├── tests/
|
|
26
|
+
└── docs/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Consequences
|
|
30
|
+
|
|
31
|
+
What becomes easier or more difficult to do and any risks introduced by the change that will need to be mitigated.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 2. Findings are the interface
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-25
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
`slarti` has two consumers with different needs. A human wants to read what is
|
|
12
|
+
wrong; an LLM agent — which has no memory of the repository between sessions —
|
|
13
|
+
needs a stable identifier to reason about and a remedy it can act on. Exit codes
|
|
14
|
+
alone carry neither.
|
|
15
|
+
|
|
16
|
+
The tool also delegates every real validation to LikeC4, LinkML and pyshacl. Its
|
|
17
|
+
own output must therefore be clearly distinguishable from theirs, and phrased in
|
|
18
|
+
domain terms rather than tool terms: "class `Finding` has no owner", not
|
|
19
|
+
`KeyError: 'owner'`.
|
|
20
|
+
|
|
21
|
+
## Decision
|
|
22
|
+
|
|
23
|
+
Findings, not exit codes, are the interface. Every finding carries a stable rule
|
|
24
|
+
ID (`OWN-1`, `REG-2`, `DOC-1`, `ENV-1`), a file, a subject named in domain terms,
|
|
25
|
+
a message and a remedy sentence. The same findings render as text or as JSON;
|
|
26
|
+
`--json` changes the encoding, never the content. IDs are permanent — retired
|
|
27
|
+
checks are never reused — and `slarti report --json` describes all of them.
|
|
28
|
+
|
|
29
|
+
Because the findings are the contract, the entities they are about are modelled
|
|
30
|
+
first-class: `Finding`, `Report`, `Constraint` and `Enforcer` are classes in
|
|
31
|
+
`model/schema/slarti.yaml`, with SHACL shapes over them (`D1`, `D2`, `D3`) and a
|
|
32
|
+
negative fixture per shape.
|
|
33
|
+
|
|
34
|
+
Diagnostics from the delegated tools are passed through unaltered and never
|
|
35
|
+
reformatted as findings.
|
|
36
|
+
|
|
37
|
+
## Consequences
|
|
38
|
+
|
|
39
|
+
- Agents can converge: run `slarti check --json`, apply the remedy, re-run.
|
|
40
|
+
- Message wording is part of the contract, and is covered by tests.
|
|
41
|
+
- Adding a check means adding an ID, a remedy sentence and a catalogue entry —
|
|
42
|
+
slightly more work than raising an exception, deliberately.
|
|
43
|
+
- Rules that no model can enforce are recorded as `enforced_by: none` with a
|
|
44
|
+
reason and surface in the unverified-invariants table rather than being silently
|
|
45
|
+
absent.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 3. The Python interfaces are a LinkML projection
|
|
2
|
+
|
|
3
|
+
Date: 2026-07-25
|
|
4
|
+
|
|
5
|
+
## Status
|
|
6
|
+
|
|
7
|
+
Accepted
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
ADR-0002 made the findings the interface and modelled the entities they are about
|
|
12
|
+
— `Finding`, `Report`, `Constraint`, `Enforcer` — as classes in
|
|
13
|
+
`model/schema/slarti.yaml`. The implementation nevertheless carried its own
|
|
14
|
+
hand-written dataclasses with the same names and roughly the same fields.
|
|
15
|
+
|
|
16
|
+
Two definitions of one entity is exactly the drift `slarti` exists to catch. The
|
|
17
|
+
schema could gain a slot the code never reads, or the code could gain a field the
|
|
18
|
+
shapes never see, and nothing would fail. The seam checks could not help: they
|
|
19
|
+
join LikeC4 to LinkML and the registry to the models, not the schema to the
|
|
20
|
+
Python that claims to implement it.
|
|
21
|
+
|
|
22
|
+
LinkML is a schema language whose point is projection: one definition, generated
|
|
23
|
+
into whatever shape a consumer needs — SHACL for pyshacl, JSON Schema for an
|
|
24
|
+
agent, Pydantic or TypeScript for an implementation. Hand-writing the Python was
|
|
25
|
+
declining the one thing LinkML is best at.
|
|
26
|
+
|
|
27
|
+
## Decision
|
|
28
|
+
|
|
29
|
+
`slarti/domain.py` is generated from `model/schema/slarti.yaml` by `gen-pydantic`
|
|
30
|
+
(`make domain`) and committed. It is the only definition of the domain entities;
|
|
31
|
+
`findings.py`, `registry.py`, `models.py`, `inject.py` and `env.py` import from it
|
|
32
|
+
rather than declaring their own.
|
|
33
|
+
|
|
34
|
+
Behaviour stays hand-written. The generated classes carry shape — fields, ranges,
|
|
35
|
+
cardinality, permissible values — and modules add the verbs around them
|
|
36
|
+
(`Report.as_schema`, `registry.is_unenforced`, `Likec4Model.owners_of`). The
|
|
37
|
+
serialised report is built as the generated `Report`, so `slarti check --json`
|
|
38
|
+
emits the shape the schema declares by construction.
|
|
39
|
+
|
|
40
|
+
Drift between the schema and the committed projection is a test failure
|
|
41
|
+
(`tests/test_domain.py`), the same gate `slarti docs --check` applies to the
|
|
42
|
+
document.
|
|
43
|
+
|
|
44
|
+
## Consequences
|
|
45
|
+
|
|
46
|
+
- A slot added to the schema is a field in the implementation after `make domain`;
|
|
47
|
+
a field the implementation wants must be argued for in the schema first.
|
|
48
|
+
- An enforcer kind that is not in the `EnforcerKind` enum is now rejected when the
|
|
49
|
+
registry is read, with the permissible values named.
|
|
50
|
+
- The generated module is excluded from ruff, mypy and vulture: the schema is
|
|
51
|
+
reviewed, its projection is not.
|
|
52
|
+
- `pydantic` becomes a runtime dependency.
|
|
53
|
+
- The same schema still generates SHACL, JSON Schema and TypeScript for other
|
|
54
|
+
consumers — the projection to Python is one target among several, not a fork.
|