arklint 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.
- arklint-0.1.0/LICENSE +21 -0
- arklint-0.1.0/PKG-INFO +230 -0
- arklint-0.1.0/README.md +203 -0
- arklint-0.1.0/pyproject.toml +45 -0
- arklint-0.1.0/setup.cfg +4 -0
- arklint-0.1.0/src/arklint/__init__.py +1 -0
- arklint-0.1.0/src/arklint/cli.py +173 -0
- arklint-0.1.0/src/arklint/config.py +99 -0
- arklint-0.1.0/src/arklint/engine.py +61 -0
- arklint-0.1.0/src/arklint/init_templates.py +77 -0
- arklint-0.1.0/src/arklint/parsers/__init__.py +5 -0
- arklint-0.1.0/src/arklint/parsers/deps.py +107 -0
- arklint-0.1.0/src/arklint/parsers/imports.py +188 -0
- arklint-0.1.0/src/arklint/parsers/patterns.py +32 -0
- arklint-0.1.0/src/arklint/reporter.py +106 -0
- arklint-0.1.0/src/arklint/rules/__init__.py +22 -0
- arklint-0.1.0/src/arklint/rules/base.py +75 -0
- arklint-0.1.0/src/arklint/rules/boundary.py +64 -0
- arklint-0.1.0/src/arklint/rules/dependency.py +96 -0
- arklint-0.1.0/src/arklint/rules/file_pattern.py +67 -0
- arklint-0.1.0/src/arklint/rules/layer_boundary.py +108 -0
- arklint-0.1.0/src/arklint/rules/pattern_ban.py +62 -0
- arklint-0.1.0/src/arklint/scanner.py +48 -0
- arklint-0.1.0/src/arklint.egg-info/PKG-INFO +230 -0
- arklint-0.1.0/src/arklint.egg-info/SOURCES.txt +28 -0
- arklint-0.1.0/src/arklint.egg-info/dependency_links.txt +1 -0
- arklint-0.1.0/src/arklint.egg-info/entry_points.txt +2 -0
- arklint-0.1.0/src/arklint.egg-info/requires.txt +8 -0
- arklint-0.1.0/src/arklint.egg-info/top_level.txt +1 -0
- arklint-0.1.0/tests/test_config.py +65 -0
arklint-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaushik K
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
arklint-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: arklint
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The architectural rulebook for your codebase. Prevention, not detection.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: architecture,linting,cli,code-quality,ai
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Environment :: Console
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: typer>=0.12.0
|
|
20
|
+
Requires-Dist: rich>=13.0.0
|
|
21
|
+
Requires-Dist: PyYAML>=6.0
|
|
22
|
+
Requires-Dist: pathspec>=0.12.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov>=5.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# Arklint
|
|
29
|
+
|
|
30
|
+
> The architectural rulebook for your codebase. Prevention, not detection.
|
|
31
|
+
|
|
32
|
+
[](https://pypi.org/project/arklint/)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+
[](https://www.python.org/)
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
Arklint enforces **architectural rules** before bad code ever lands — whether written by AI agents or humans. It's language-agnostic, runs locally with zero cloud dependency, and takes 60 seconds to set up.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
$ arklint check
|
|
42
|
+
|
|
43
|
+
Arklint v0.1.0 — Scanning 142 files against 5 rules...
|
|
44
|
+
|
|
45
|
+
✗ FAIL no-direct-db-in-routes
|
|
46
|
+
API routes must not import database modules directly
|
|
47
|
+
routes/users.py → imports 'sqlalchemy' — blocked by this rule
|
|
48
|
+
routes/orders.py → imports 'psycopg2' — blocked by this rule
|
|
49
|
+
|
|
50
|
+
✗ FAIL single-http-client
|
|
51
|
+
conflicting packages — keep exactly one of requests, httpx
|
|
52
|
+
|
|
53
|
+
⚠ WARN no-print-statements
|
|
54
|
+
services/email.py:45 → banned pattern matched: 'print('
|
|
55
|
+
|
|
56
|
+
✓ PASS models-in-models-dir
|
|
57
|
+
✓ PASS layered-architecture
|
|
58
|
+
|
|
59
|
+
────────────────────────────────────────────────────────
|
|
60
|
+
Results: 2 errors, 1 warning, 2 passed
|
|
61
|
+
────────────────────────────────────────────────────────
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Why Arklint
|
|
65
|
+
|
|
66
|
+
| Other tools | Arklint |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Review **after** code is written (PR comments) | Enforce **before** (pre-commit, CI gate) |
|
|
69
|
+
| Focus on syntax, style, security | Focus on **architecture and boundaries** |
|
|
70
|
+
| SaaS, $15–30/user/month | Open-source, free, runs locally |
|
|
71
|
+
| Complex setup | `arklint init` → working in 60 seconds |
|
|
72
|
+
|
|
73
|
+
Arklint is **not** a linter (ESLint/Ruff), **not** a security scanner (Semgrep/Snyk), and **not** an AI reviewer (CodeRabbit). It operates at the architecture level — patterns, conventions, and structural rules that no existing tool enforces.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Installation
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
pip install arklint
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick start
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 1. Generate a starter config
|
|
87
|
+
arklint init
|
|
88
|
+
|
|
89
|
+
# 2. Edit .arklint.yml to match your architecture (takes 2 minutes)
|
|
90
|
+
|
|
91
|
+
# 3. Run a check
|
|
92
|
+
arklint check
|
|
93
|
+
|
|
94
|
+
# 4. Add to pre-commit or CI
|
|
95
|
+
arklint check --strict # exits 1 on warnings too
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Rule types
|
|
101
|
+
|
|
102
|
+
### `boundary` — Import restrictions between directories
|
|
103
|
+
|
|
104
|
+
Prevent files in source directories from importing blocked packages.
|
|
105
|
+
|
|
106
|
+
```yaml
|
|
107
|
+
- id: no-direct-db-in-routes
|
|
108
|
+
type: boundary
|
|
109
|
+
description: "API routes must not import the database layer directly"
|
|
110
|
+
source: "routes/**"
|
|
111
|
+
blocked_imports:
|
|
112
|
+
- "sqlalchemy"
|
|
113
|
+
- "psycopg2"
|
|
114
|
+
- "pymongo"
|
|
115
|
+
severity: error
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `dependency` — Control what packages are in the project
|
|
119
|
+
|
|
120
|
+
Detect conflicting or banned dependencies in `requirements.txt`, `package.json`, `go.mod`, and more.
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
- id: single-http-client
|
|
124
|
+
type: dependency
|
|
125
|
+
description: "Pick one HTTP client and stick with it"
|
|
126
|
+
allow_only_one_of:
|
|
127
|
+
- "requests"
|
|
128
|
+
- "httpx"
|
|
129
|
+
- "aiohttp"
|
|
130
|
+
severity: error
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### `file-pattern` — Code patterns only allowed in specific directories
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
- id: models-in-models-dir
|
|
137
|
+
type: file-pattern
|
|
138
|
+
description: "Data models must live in models/ or schemas/"
|
|
139
|
+
pattern: 'class\s+\w*(Model|Schema)\s*[:(]'
|
|
140
|
+
allowed_in:
|
|
141
|
+
- "models/**"
|
|
142
|
+
- "schemas/**"
|
|
143
|
+
severity: warning
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### `pattern-ban` — Ban a pattern across the codebase
|
|
147
|
+
|
|
148
|
+
```yaml
|
|
149
|
+
- id: no-print-statements
|
|
150
|
+
type: pattern-ban
|
|
151
|
+
description: "Use structured logging, not print()"
|
|
152
|
+
pattern: 'print\('
|
|
153
|
+
exclude:
|
|
154
|
+
- "tests/**"
|
|
155
|
+
- "scripts/**"
|
|
156
|
+
severity: warning
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `layer-boundary` — Enforce layered architecture
|
|
160
|
+
|
|
161
|
+
Control which layers are allowed to import from which.
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
- id: layered-architecture
|
|
165
|
+
type: layer-boundary
|
|
166
|
+
description: "Enforce routes → services → repositories"
|
|
167
|
+
layers:
|
|
168
|
+
- name: routes
|
|
169
|
+
path: "routes/**"
|
|
170
|
+
- name: services
|
|
171
|
+
path: "services/**"
|
|
172
|
+
- name: repositories
|
|
173
|
+
path: "repositories/**"
|
|
174
|
+
allowed_dependencies:
|
|
175
|
+
routes: [services]
|
|
176
|
+
services: [repositories]
|
|
177
|
+
repositories: []
|
|
178
|
+
severity: error
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## CLI reference
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
arklint init Create a starter .arklint.yml
|
|
187
|
+
arklint init --force Overwrite existing config
|
|
188
|
+
|
|
189
|
+
arklint check Scan from current directory
|
|
190
|
+
arklint check ./src Scan a specific directory
|
|
191
|
+
arklint check --strict Exit 1 on warnings too
|
|
192
|
+
arklint check --json Machine-readable JSON output
|
|
193
|
+
arklint check -c path/to/.arklint.yml Use a specific config
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## CI integration
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
# GitHub Actions
|
|
200
|
+
- name: Arklint
|
|
201
|
+
run: |
|
|
202
|
+
pip install arklint
|
|
203
|
+
arklint check --strict
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## pre-commit
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
- repo: local
|
|
210
|
+
hooks:
|
|
211
|
+
- id: arklint
|
|
212
|
+
name: arklint
|
|
213
|
+
entry: arklint check
|
|
214
|
+
language: python
|
|
215
|
+
pass_filenames: false
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Supported languages
|
|
221
|
+
|
|
222
|
+
Import extraction works for: Python, JavaScript, TypeScript, Go, Ruby, Rust, Java, C#, PHP.
|
|
223
|
+
|
|
224
|
+
Dependency parsing works for: `requirements.txt`, `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## License
|
|
229
|
+
|
|
230
|
+
MIT — see [LICENSE](LICENSE).
|
arklint-0.1.0/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Arklint
|
|
2
|
+
|
|
3
|
+
> The architectural rulebook for your codebase. Prevention, not detection.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/arklint/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://www.python.org/)
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
Arklint enforces **architectural rules** before bad code ever lands — whether written by AI agents or humans. It's language-agnostic, runs locally with zero cloud dependency, and takes 60 seconds to set up.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
$ arklint check
|
|
15
|
+
|
|
16
|
+
Arklint v0.1.0 — Scanning 142 files against 5 rules...
|
|
17
|
+
|
|
18
|
+
✗ FAIL no-direct-db-in-routes
|
|
19
|
+
API routes must not import database modules directly
|
|
20
|
+
routes/users.py → imports 'sqlalchemy' — blocked by this rule
|
|
21
|
+
routes/orders.py → imports 'psycopg2' — blocked by this rule
|
|
22
|
+
|
|
23
|
+
✗ FAIL single-http-client
|
|
24
|
+
conflicting packages — keep exactly one of requests, httpx
|
|
25
|
+
|
|
26
|
+
⚠ WARN no-print-statements
|
|
27
|
+
services/email.py:45 → banned pattern matched: 'print('
|
|
28
|
+
|
|
29
|
+
✓ PASS models-in-models-dir
|
|
30
|
+
✓ PASS layered-architecture
|
|
31
|
+
|
|
32
|
+
────────────────────────────────────────────────────────
|
|
33
|
+
Results: 2 errors, 1 warning, 2 passed
|
|
34
|
+
────────────────────────────────────────────────────────
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Why Arklint
|
|
38
|
+
|
|
39
|
+
| Other tools | Arklint |
|
|
40
|
+
|---|---|
|
|
41
|
+
| Review **after** code is written (PR comments) | Enforce **before** (pre-commit, CI gate) |
|
|
42
|
+
| Focus on syntax, style, security | Focus on **architecture and boundaries** |
|
|
43
|
+
| SaaS, $15–30/user/month | Open-source, free, runs locally |
|
|
44
|
+
| Complex setup | `arklint init` → working in 60 seconds |
|
|
45
|
+
|
|
46
|
+
Arklint is **not** a linter (ESLint/Ruff), **not** a security scanner (Semgrep/Snyk), and **not** an AI reviewer (CodeRabbit). It operates at the architecture level — patterns, conventions, and structural rules that no existing tool enforces.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install arklint
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Quick start
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 1. Generate a starter config
|
|
60
|
+
arklint init
|
|
61
|
+
|
|
62
|
+
# 2. Edit .arklint.yml to match your architecture (takes 2 minutes)
|
|
63
|
+
|
|
64
|
+
# 3. Run a check
|
|
65
|
+
arklint check
|
|
66
|
+
|
|
67
|
+
# 4. Add to pre-commit or CI
|
|
68
|
+
arklint check --strict # exits 1 on warnings too
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Rule types
|
|
74
|
+
|
|
75
|
+
### `boundary` — Import restrictions between directories
|
|
76
|
+
|
|
77
|
+
Prevent files in source directories from importing blocked packages.
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
- id: no-direct-db-in-routes
|
|
81
|
+
type: boundary
|
|
82
|
+
description: "API routes must not import the database layer directly"
|
|
83
|
+
source: "routes/**"
|
|
84
|
+
blocked_imports:
|
|
85
|
+
- "sqlalchemy"
|
|
86
|
+
- "psycopg2"
|
|
87
|
+
- "pymongo"
|
|
88
|
+
severity: error
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `dependency` — Control what packages are in the project
|
|
92
|
+
|
|
93
|
+
Detect conflicting or banned dependencies in `requirements.txt`, `package.json`, `go.mod`, and more.
|
|
94
|
+
|
|
95
|
+
```yaml
|
|
96
|
+
- id: single-http-client
|
|
97
|
+
type: dependency
|
|
98
|
+
description: "Pick one HTTP client and stick with it"
|
|
99
|
+
allow_only_one_of:
|
|
100
|
+
- "requests"
|
|
101
|
+
- "httpx"
|
|
102
|
+
- "aiohttp"
|
|
103
|
+
severity: error
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `file-pattern` — Code patterns only allowed in specific directories
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
- id: models-in-models-dir
|
|
110
|
+
type: file-pattern
|
|
111
|
+
description: "Data models must live in models/ or schemas/"
|
|
112
|
+
pattern: 'class\s+\w*(Model|Schema)\s*[:(]'
|
|
113
|
+
allowed_in:
|
|
114
|
+
- "models/**"
|
|
115
|
+
- "schemas/**"
|
|
116
|
+
severity: warning
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### `pattern-ban` — Ban a pattern across the codebase
|
|
120
|
+
|
|
121
|
+
```yaml
|
|
122
|
+
- id: no-print-statements
|
|
123
|
+
type: pattern-ban
|
|
124
|
+
description: "Use structured logging, not print()"
|
|
125
|
+
pattern: 'print\('
|
|
126
|
+
exclude:
|
|
127
|
+
- "tests/**"
|
|
128
|
+
- "scripts/**"
|
|
129
|
+
severity: warning
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `layer-boundary` — Enforce layered architecture
|
|
133
|
+
|
|
134
|
+
Control which layers are allowed to import from which.
|
|
135
|
+
|
|
136
|
+
```yaml
|
|
137
|
+
- id: layered-architecture
|
|
138
|
+
type: layer-boundary
|
|
139
|
+
description: "Enforce routes → services → repositories"
|
|
140
|
+
layers:
|
|
141
|
+
- name: routes
|
|
142
|
+
path: "routes/**"
|
|
143
|
+
- name: services
|
|
144
|
+
path: "services/**"
|
|
145
|
+
- name: repositories
|
|
146
|
+
path: "repositories/**"
|
|
147
|
+
allowed_dependencies:
|
|
148
|
+
routes: [services]
|
|
149
|
+
services: [repositories]
|
|
150
|
+
repositories: []
|
|
151
|
+
severity: error
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## CLI reference
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
arklint init Create a starter .arklint.yml
|
|
160
|
+
arklint init --force Overwrite existing config
|
|
161
|
+
|
|
162
|
+
arklint check Scan from current directory
|
|
163
|
+
arklint check ./src Scan a specific directory
|
|
164
|
+
arklint check --strict Exit 1 on warnings too
|
|
165
|
+
arklint check --json Machine-readable JSON output
|
|
166
|
+
arklint check -c path/to/.arklint.yml Use a specific config
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## CI integration
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
# GitHub Actions
|
|
173
|
+
- name: Arklint
|
|
174
|
+
run: |
|
|
175
|
+
pip install arklint
|
|
176
|
+
arklint check --strict
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## pre-commit
|
|
180
|
+
|
|
181
|
+
```yaml
|
|
182
|
+
- repo: local
|
|
183
|
+
hooks:
|
|
184
|
+
- id: arklint
|
|
185
|
+
name: arklint
|
|
186
|
+
entry: arklint check
|
|
187
|
+
language: python
|
|
188
|
+
pass_filenames: false
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Supported languages
|
|
194
|
+
|
|
195
|
+
Import extraction works for: Python, JavaScript, TypeScript, Go, Ruby, Rust, Java, C#, PHP.
|
|
196
|
+
|
|
197
|
+
Dependency parsing works for: `requirements.txt`, `package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `Gemfile`.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## License
|
|
202
|
+
|
|
203
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "arklint"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "The architectural rulebook for your codebase. Prevention, not detection."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["architecture", "linting", "cli", "code-quality", "ai"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"typer>=0.12.0",
|
|
27
|
+
"rich>=13.0.0",
|
|
28
|
+
"PyYAML>=6.0",
|
|
29
|
+
"pathspec>=0.12.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
arklint = "arklint.cli:app"
|
|
34
|
+
|
|
35
|
+
[project.optional-dependencies]
|
|
36
|
+
dev = [
|
|
37
|
+
"pytest>=8.0",
|
|
38
|
+
"pytest-cov>=5.0",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.packages.find]
|
|
42
|
+
where = ["src"]
|
|
43
|
+
|
|
44
|
+
[tool.pytest.ini_options]
|
|
45
|
+
testpaths = ["tests"]
|
arklint-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
"""Arklint CLI entry point.
|
|
2
|
+
|
|
3
|
+
Commands
|
|
4
|
+
--------
|
|
5
|
+
arklint init Create a starter .arklint.yml in the current directory.
|
|
6
|
+
arklint check Scan the codebase against all configured rules.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from arklint import __version__
|
|
16
|
+
from arklint.config import load_config, ConfigError
|
|
17
|
+
from arklint.engine import run_rules
|
|
18
|
+
from arklint.init_templates import STARTER_TEMPLATE
|
|
19
|
+
from arklint.reporter import console, err_console, print_header, print_report
|
|
20
|
+
from arklint.scanner import collect_files
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
app = typer.Typer(
|
|
24
|
+
name="arklint",
|
|
25
|
+
help="The architectural rulebook for your codebase. Prevention, not detection.",
|
|
26
|
+
add_completion=False,
|
|
27
|
+
no_args_is_help=True,
|
|
28
|
+
rich_markup_mode="rich",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
# arklint init
|
|
34
|
+
# ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
def init(
|
|
38
|
+
force: bool = typer.Option(
|
|
39
|
+
False, "--force", "-f", help="Overwrite an existing .arklint.yml."
|
|
40
|
+
),
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Create a starter [bold].arklint.yml[/bold] in the current directory."""
|
|
43
|
+
target = Path.cwd() / ".arklint.yml"
|
|
44
|
+
|
|
45
|
+
if target.exists() and not force:
|
|
46
|
+
err_console.print(
|
|
47
|
+
"[yellow].arklint.yml already exists.[/yellow] "
|
|
48
|
+
"Use [bold]--force[/bold] to overwrite."
|
|
49
|
+
)
|
|
50
|
+
raise typer.Exit(1)
|
|
51
|
+
|
|
52
|
+
target.write_text(STARTER_TEMPLATE)
|
|
53
|
+
console.print(
|
|
54
|
+
"[bold green]✓[/bold green] Created [cyan].arklint.yml[/cyan] with starter rules.\n"
|
|
55
|
+
" Edit it to match your architecture, then run [bold]arklint check[/bold]."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
# arklint check
|
|
61
|
+
# ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
@app.command()
|
|
64
|
+
def check(
|
|
65
|
+
path: Optional[Path] = typer.Argument(
|
|
66
|
+
None,
|
|
67
|
+
help="Directory to scan. Defaults to the current directory.",
|
|
68
|
+
show_default=False,
|
|
69
|
+
),
|
|
70
|
+
config: Optional[Path] = typer.Option(
|
|
71
|
+
None,
|
|
72
|
+
"--config",
|
|
73
|
+
"-c",
|
|
74
|
+
help="Path to .arklint.yml. Auto-discovered if omitted.",
|
|
75
|
+
),
|
|
76
|
+
strict: bool = typer.Option(
|
|
77
|
+
False,
|
|
78
|
+
"--strict",
|
|
79
|
+
help="Treat warnings as errors (exit code 1).",
|
|
80
|
+
),
|
|
81
|
+
json_output: bool = typer.Option(
|
|
82
|
+
False,
|
|
83
|
+
"--json",
|
|
84
|
+
help="Emit violations as JSON (useful for CI integrations).",
|
|
85
|
+
),
|
|
86
|
+
) -> None:
|
|
87
|
+
"""Scan the codebase against your architectural rules."""
|
|
88
|
+
scan_root = (path or Path.cwd()).resolve()
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
cfg = load_config(config)
|
|
92
|
+
except ConfigError as exc:
|
|
93
|
+
err_console.print(f"[bold red]Config error:[/bold red] {exc}")
|
|
94
|
+
raise typer.Exit(2) from exc
|
|
95
|
+
|
|
96
|
+
files = collect_files(scan_root)
|
|
97
|
+
|
|
98
|
+
if json_output:
|
|
99
|
+
_check_json(cfg, files, scan_root, strict)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
print_header(__version__, len(files), len(cfg.rules))
|
|
103
|
+
results = run_rules(cfg, files, scan_root=scan_root)
|
|
104
|
+
errors, warnings = print_report(results, scan_root)
|
|
105
|
+
|
|
106
|
+
if errors > 0 or (strict and warnings > 0):
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# --version flag (attached to the root callback)
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
@app.callback(invoke_without_command=True)
|
|
115
|
+
def _root(
|
|
116
|
+
ctx: typer.Context,
|
|
117
|
+
version: bool = typer.Option(
|
|
118
|
+
False, "--version", "-v", is_eager=True, help="Show version and exit."
|
|
119
|
+
),
|
|
120
|
+
) -> None:
|
|
121
|
+
if version:
|
|
122
|
+
console.print(f"arklint v{__version__}")
|
|
123
|
+
raise typer.Exit()
|
|
124
|
+
if ctx.invoked_subcommand is None:
|
|
125
|
+
console.print(ctx.get_help())
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# ---------------------------------------------------------------------------
|
|
129
|
+
# JSON output helper
|
|
130
|
+
# ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
def _check_json(cfg, files, scan_root, strict: bool) -> None:
|
|
133
|
+
import json
|
|
134
|
+
|
|
135
|
+
results = run_rules(cfg, files, scan_root=scan_root)
|
|
136
|
+
output = []
|
|
137
|
+
errors = 0
|
|
138
|
+
warnings = 0
|
|
139
|
+
for result in results:
|
|
140
|
+
for v in result.violations:
|
|
141
|
+
try:
|
|
142
|
+
rel = str(v.file.relative_to(scan_root))
|
|
143
|
+
except ValueError:
|
|
144
|
+
rel = str(v.file)
|
|
145
|
+
output.append(
|
|
146
|
+
{
|
|
147
|
+
"rule": v.rule_id,
|
|
148
|
+
"severity": v.severity,
|
|
149
|
+
"file": rel,
|
|
150
|
+
"line": v.line,
|
|
151
|
+
"message": v.message,
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
if v.severity == "error":
|
|
155
|
+
errors += 1
|
|
156
|
+
else:
|
|
157
|
+
warnings += 1
|
|
158
|
+
|
|
159
|
+
console.print(json.dumps(output, indent=2))
|
|
160
|
+
if errors > 0 or (strict and warnings > 0):
|
|
161
|
+
raise typer.Exit(1)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
# ---------------------------------------------------------------------------
|
|
165
|
+
# Entrypoint
|
|
166
|
+
# ---------------------------------------------------------------------------
|
|
167
|
+
|
|
168
|
+
def main() -> None:
|
|
169
|
+
app()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|