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 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
+ [![PyPI version](https://badge.fury.io/py/arklint.svg)](https://pypi.org/project/arklint/)
33
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
34
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](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).
@@ -0,0 +1,203 @@
1
+ # Arklint
2
+
3
+ > The architectural rulebook for your codebase. Prevention, not detection.
4
+
5
+ [![PyPI version](https://badge.fury.io/py/arklint.svg)](https://pypi.org/project/arklint/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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()