smelt-cli 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.
Files changed (81) hide show
  1. smelt_cli-0.1.0/PKG-INFO +199 -0
  2. smelt_cli-0.1.0/README.md +187 -0
  3. smelt_cli-0.1.0/pyproject.toml +117 -0
  4. smelt_cli-0.1.0/pyproject.toml.orig +116 -0
  5. smelt_cli-0.1.0/smelt/__init__.py +6 -0
  6. smelt_cli-0.1.0/smelt/__main__.py +5 -0
  7. smelt_cli-0.1.0/smelt/analysis/__init__.py +27 -0
  8. smelt_cli-0.1.0/smelt/analysis/context.py +106 -0
  9. smelt_cli-0.1.0/smelt/analysis/files.py +198 -0
  10. smelt_cli-0.1.0/smelt/analysis/graphs.py +77 -0
  11. smelt_cli-0.1.0/smelt/analysis/imports.py +303 -0
  12. smelt_cli-0.1.0/smelt/analysis/parsing.py +110 -0
  13. smelt_cli-0.1.0/smelt/analysis/roles.py +122 -0
  14. smelt_cli-0.1.0/smelt/analysis/syntax.py +246 -0
  15. smelt_cli-0.1.0/smelt/analysis/types.py +181 -0
  16. smelt_cli-0.1.0/smelt/cli/__init__.py +3 -0
  17. smelt_cli-0.1.0/smelt/cli/app.py +140 -0
  18. smelt_cli-0.1.0/smelt/cli/commands/__init__.py +18 -0
  19. smelt_cli-0.1.0/smelt/cli/commands/adopt.py +116 -0
  20. smelt_cli-0.1.0/smelt/cli/commands/check.py +80 -0
  21. smelt_cli-0.1.0/smelt/cli/commands/discover.py +45 -0
  22. smelt_cli-0.1.0/smelt/cli/commands/info.py +107 -0
  23. smelt_cli-0.1.0/smelt/cli/commands/verify.py +60 -0
  24. smelt_cli-0.1.0/smelt/cli/support.py +64 -0
  25. smelt_cli-0.1.0/smelt/config/__init__.py +24 -0
  26. smelt_cli-0.1.0/smelt/config/discovery.py +49 -0
  27. smelt_cli-0.1.0/smelt/config/errors.py +51 -0
  28. smelt_cli-0.1.0/smelt/config/loader.py +182 -0
  29. smelt_cli-0.1.0/smelt/config/models.py +452 -0
  30. smelt_cli-0.1.0/smelt/config/patterns.py +47 -0
  31. smelt_cli-0.1.0/smelt/config/schema.py +23 -0
  32. smelt_cli-0.1.0/smelt/config/validation.py +193 -0
  33. smelt_cli-0.1.0/smelt/diagnostics/__init__.py +0 -0
  34. smelt_cli-0.1.0/smelt/diagnostics/debt.py +119 -0
  35. smelt_cli-0.1.0/smelt/diagnostics/dedupe.py +44 -0
  36. smelt_cli-0.1.0/smelt/diagnostics/render/__init__.py +0 -0
  37. smelt_cli-0.1.0/smelt/diagnostics/render/machine.py +137 -0
  38. smelt_cli-0.1.0/smelt/diagnostics/render/text.py +197 -0
  39. smelt_cli-0.1.0/smelt/diagnostics/report.py +61 -0
  40. smelt_cli-0.1.0/smelt/diagnostics/suppressions.py +80 -0
  41. smelt_cli-0.1.0/smelt/diagnostics/violation.py +109 -0
  42. smelt_cli-0.1.0/smelt/docs.py +97 -0
  43. smelt_cli-0.1.0/smelt/engine/__init__.py +0 -0
  44. smelt_cli-0.1.0/smelt/engine/architecture_map.py +125 -0
  45. smelt_cli-0.1.0/smelt/engine/briefing.py +364 -0
  46. smelt_cli-0.1.0/smelt/engine/changes.py +81 -0
  47. smelt_cli-0.1.0/smelt/engine/check.py +393 -0
  48. smelt_cli-0.1.0/smelt/engine/inference.py +457 -0
  49. smelt_cli-0.1.0/smelt/engine/verify.py +77 -0
  50. smelt_cli-0.1.0/smelt/model/__init__.py +9 -0
  51. smelt_cli-0.1.0/smelt/model/architecture.py +237 -0
  52. smelt_cli-0.1.0/smelt/py.typed +0 -0
  53. smelt_cli-0.1.0/smelt/rules/__init__.py +0 -0
  54. smelt_cli-0.1.0/smelt/rules/base.py +142 -0
  55. smelt_cli-0.1.0/smelt/rules/code/__init__.py +0 -0
  56. smelt_cli-0.1.0/smelt/rules/code/common.py +119 -0
  57. smelt_cli-0.1.0/smelt/rules/code/construction.py +172 -0
  58. smelt_cli-0.1.0/smelt/rules/code/container.py +104 -0
  59. smelt_cli-0.1.0/smelt/rules/code/inheritance.py +99 -0
  60. smelt_cli-0.1.0/smelt/rules/code/roles.py +100 -0
  61. smelt_cli-0.1.0/smelt/rules/code/self_reference.py +104 -0
  62. smelt_cli-0.1.0/smelt/rules/common.py +127 -0
  63. smelt_cli-0.1.0/smelt/rules/dependencies/__init__.py +0 -0
  64. smelt_cli-0.1.0/smelt/rules/dependencies/composition_root.py +123 -0
  65. smelt_cli-0.1.0/smelt/rules/dependencies/cycles.py +207 -0
  66. smelt_cli-0.1.0/smelt/rules/dependencies/features.py +132 -0
  67. smelt_cli-0.1.0/smelt/rules/dependencies/layers.py +220 -0
  68. smelt_cli-0.1.0/smelt/rules/dependencies/third_party.py +107 -0
  69. smelt_cli-0.1.0/smelt/rules/meta.py +72 -0
  70. smelt_cli-0.1.0/smelt/rules/registry.py +135 -0
  71. smelt_cli-0.1.0/smelt/rules/structure/__init__.py +0 -0
  72. smelt_cli-0.1.0/smelt/rules/structure/layout.py +213 -0
  73. smelt_cli-0.1.0/smelt/rules/structure/naming.py +63 -0
  74. smelt_cli-0.1.0/smelt/rules/structure/roles.py +63 -0
  75. smelt_cli-0.1.0/smelt/rules/testing/__init__.py +0 -0
  76. smelt_cli-0.1.0/smelt/rules/testing/api.py +115 -0
  77. smelt_cli-0.1.0/smelt/rules/testing/bloat.py +84 -0
  78. smelt_cli-0.1.0/smelt/rules/testing/common.py +238 -0
  79. smelt_cli-0.1.0/smelt/rules/testing/location.py +253 -0
  80. smelt_cli-0.1.0/smelt/rules/testing/mocks.py +294 -0
  81. smelt_cli-0.1.0/smelt/rules/testing/patching.py +152 -0
@@ -0,0 +1,199 @@
1
+ Metadata-Version: 2.3
2
+ Name: smelt-cli
3
+ Version: 0.1.0
4
+ Summary: Static guardrails for Python codebases maintained by humans and coding agents.
5
+ Author: Mathis Arends
6
+ Author-email: Mathis Arends <mathisarends27@gmail.com>
7
+ Requires-Dist: grimp>=3.17
8
+ Requires-Dist: pydantic>=2.13.5
9
+ Requires-Dist: pyyaml>=6.0.3
10
+ Requires-Python: >=3.12
11
+ Description-Content-Type: text/markdown
12
+
13
+ # smelt
14
+
15
+ Static architecture guardrails for Python. Describe your features, layers and roles in
16
+ `smelt.yaml`; `smelt check` reports every import and construct that breaks them, with the
17
+ exact location, the allowed alternative and a hint on how to fix it.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ uv tool install smelt-cli
23
+ # or install into your project's environment:
24
+ pip install smelt-cli
25
+ ```
26
+
27
+ The PyPI package is named `smelt-cli`; the CLI command and Python package are both
28
+ named `smelt`. To run without installing, use `uvx --from smelt-cli smelt check`.
29
+
30
+ ## Usage
31
+
32
+ ```bash
33
+ smelt check # whole project, text output
34
+ smelt check --changed # only files changed against HEAD (incl. untracked)
35
+ smelt check --changed --base origin/main --format json
36
+ smelt context voice # architecture briefing for a feature or path
37
+ smelt explain SMT101 # rationale, examples and config knobs of a rule
38
+ smelt rules # all rules with defaults
39
+ smelt debt # record today's violations as known debt
40
+ smelt debt --prune # drop debt entries that were fixed
41
+ smelt init # draft a config from packages or uv workspace members
42
+ ```
43
+
44
+ `smelt debt` lets an existing project adopt smelt incrementally: with
45
+ `findings.debt: .smelt/debt.json` in `smelt.yaml`, `smelt check` only fails on new violations, and SMT903 reports entries that
46
+ were fixed and can leave the file.
47
+
48
+ Exit codes: `0` clean, `1` violations at or above `--fail-on`, `2` config or usage error.
49
+
50
+ ## Configuration
51
+
52
+ `smelt.yaml` has one section per question:
53
+
54
+ | Section | Answers |
55
+ |---|---|
56
+ | `project` | Where is the code? (`source_roots`, `test_roots`; packages are discovered) |
57
+ | `architecture` | What shape should it have? (features, layers, shared, composition root, cross-feature relationships, cycles, roles) |
58
+ | `conventions` | How is code named, placed and tested? (`naming`, `packages`, `tests`) |
59
+ | `integrations` | Where does a framework get special treatment? (`dependency_injection`) |
60
+ | `analysis` | How does smelt read the code? (`imports`, `types`) |
61
+ | `rules` | How loud is a finding? Severities only, by rule name or code |
62
+ | `findings` | How are existing findings handled? (`debt`, `ignore`, `suppressions`) |
63
+
64
+ `smelt config show` prints the resolved config with all defaults; [docs/configuration.md](docs/configuration.md) explains the layout.
65
+
66
+ For a uv workspace, run `smelt init` at the workspace root. It reads
67
+ `tool.uv.workspace.members`, finds each member's source and test roots, and drafts one
68
+ configuration for all packages. Review the generated policy before adopting its findings:
69
+ feature/layer boundaries are inferred, not a declaration of your intended architecture.
70
+ For example, feature-local DI providers can be allowed to use the DI framework while
71
+ remaining in their original feature and layer:
72
+
73
+ ```yaml
74
+ architecture:
75
+ features: {root: backend.features}
76
+ composition_root: [backend.main, backend.lifespan]
77
+ cross_feature:
78
+ default: deny
79
+ allow:
80
+ - from: session.presentation
81
+ to: auth.presentation
82
+
83
+ integrations:
84
+ dependency_injection:
85
+ frameworks: [dishka] # always allowed in the composition root
86
+ allowed_in: ["backend.features.*.infrastructure.di"]
87
+ ```
88
+
89
+ The object form permits only that directional feature/layer relationship. The shorter
90
+ `"presentation -> presentation"` form remains available when a global layer-pair
91
+ exception is intended. `allowed_in` entries are dotted module patterns (`*` is one
92
+ segment, `**` any number) and also cover their submodules; unlike the composition root,
93
+ those modules keep all feature and layer rules.
94
+
95
+ For an architecture-first adoption, `smelt check --select SMT1,SMT3` focuses on
96
+ dependency and structure findings. To silence noisier testing rules persistently, use
97
+ severity overrides such as `rules: {private-access: off, interaction-assertion: off}` (rule names or codes) and review them later.
98
+
99
+ Silence a single finding inline, always with a reason:
100
+
101
+ ```python
102
+ from gateway.infra.sql import Repo # smelt: ignore[SMT101] -- migration tracked in #123
103
+ ```
104
+
105
+ With `conventions.tests.layout: mirror`, every test file must mirror a source module by its path:
106
+ `tests/billing/test_invoice.py` needs `app/billing/invoice.py`, and a package test
107
+ `tests/billing/test_billing.py` needs `app/billing/`. Not every module needs a test, but a
108
+ test whose source is missing or elsewhere is an error. Deliberately unmirrored tests go in
109
+ `conventions.tests.unmirrored` (e.g. `["tests/integration/**"]`);
110
+ `conventions.tests.mirror_suffixes: true` also allows `test_invoice_<topic>.py`.
111
+ `conventions.tests.mirror` sets the convention relative to the test
112
+ root: the default `{path}/test_{module}.py` drops the root package, `{root}/{path}/test_{module}.py`
113
+ keeps it, and `unit/{path}/{module}_test.py` puts tests under `tests/unit/` with a suffix.
114
+
115
+ Every rule has a page under [docs/rules](docs/rules/), and `smelt.schema.json` gives editors
116
+ autocompletion for `smelt.yaml`.
117
+
118
+ ## Python versions
119
+
120
+ Smelt runs on Python 3.12 to 3.14 and parses your code with the Python it runs on. Code
121
+ that uses newer syntax (3.14's `except A, B:` or t-strings, 3.13's type parameter defaults)
122
+ needs smelt on that version, e.g. `uvx -p 3.14 --from smelt-cli smelt check`; the syntax error says so when
123
+ `requires-python` or `.python-version` targets a newer Python.
124
+
125
+ ## Optional type information
126
+
127
+ Role detection is nominal by default: a class is an adapter when it inherits a port. With
128
+
129
+ ```yaml
130
+ analysis:
131
+ types: pyright # needs pyright on PATH; pyright_command overrides how it is run
132
+ ```
133
+
134
+ Smelt also asks pyright whether a class satisfies a port structurally, so a duck-typed
135
+ adapter is found too. It is never required: without it, every rule still runs.
136
+
137
+ ## Using Smelt with coding agents
138
+
139
+ Add this to your `AGENTS.md` or `CLAUDE.md`:
140
+
141
+ ```md
142
+ Before implementing, run `smelt context <feature>` to see where code belongs.
143
+ After every change, run `smelt check --changed --format json`.
144
+ Do not finish while errors remain. Use `smelt explain <code>` when unsure.
145
+ ```
146
+
147
+ Suggested loop: `smelt context <feature>` → edit → `smelt check --changed` → fix → tests →
148
+ pre-commit → CI (full check). Prefer the cheapest verification that gives sufficient
149
+ confidence: a rename needs smelt plus a type checker, new behavior needs one focused
150
+ regression test.
151
+
152
+ ## pre-commit
153
+
154
+ ```yaml
155
+ repos:
156
+ - repo: https://github.com/mathisarends/smelt
157
+ rev: v0.1.0
158
+ hooks:
159
+ - id: smelt
160
+ ```
161
+
162
+ ## GitHub Actions
163
+
164
+ CI always checks the whole repository, because cycles and transitive rules cannot be judged
165
+ from a diff alone.
166
+
167
+ ```yaml
168
+ - uses: astral-sh/setup-uv@v6
169
+ - run: uvx --from smelt-cli smelt check --format github
170
+ # optional: code scanning
171
+ - run: uvx --from smelt-cli smelt check --format sarif > smelt.sarif || true
172
+ - uses: github/codeql-action/upload-sarif@v3
173
+ with:
174
+ sarif_file: smelt.sarif
175
+ ```
176
+
177
+ ## Development
178
+
179
+ Requires [uv](https://docs.astral.sh/uv/).
180
+
181
+ ```bash
182
+ uv sync # create .venv and install dev dependencies
183
+ uv run pre-commit install
184
+ ```
185
+
186
+ Common commands:
187
+
188
+ ```bash
189
+ uv run pytest # run tests
190
+ uv run pytest --cov # run tests with coverage
191
+ uv run ruff check --fix . # lint
192
+ uv run ruff format . # format
193
+ uv run mypy # type-check
194
+ uv run pre-commit run --all-files # run all hooks
195
+ uv run smelt check # smelt checks itself
196
+ uv run python scripts/generate.py # refresh smelt.schema.json and docs/rules/
197
+ ```
198
+
199
+ Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/).
@@ -0,0 +1,187 @@
1
+ # smelt
2
+
3
+ Static architecture guardrails for Python. Describe your features, layers and roles in
4
+ `smelt.yaml`; `smelt check` reports every import and construct that breaks them, with the
5
+ exact location, the allowed alternative and a hint on how to fix it.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ uv tool install smelt-cli
11
+ # or install into your project's environment:
12
+ pip install smelt-cli
13
+ ```
14
+
15
+ The PyPI package is named `smelt-cli`; the CLI command and Python package are both
16
+ named `smelt`. To run without installing, use `uvx --from smelt-cli smelt check`.
17
+
18
+ ## Usage
19
+
20
+ ```bash
21
+ smelt check # whole project, text output
22
+ smelt check --changed # only files changed against HEAD (incl. untracked)
23
+ smelt check --changed --base origin/main --format json
24
+ smelt context voice # architecture briefing for a feature or path
25
+ smelt explain SMT101 # rationale, examples and config knobs of a rule
26
+ smelt rules # all rules with defaults
27
+ smelt debt # record today's violations as known debt
28
+ smelt debt --prune # drop debt entries that were fixed
29
+ smelt init # draft a config from packages or uv workspace members
30
+ ```
31
+
32
+ `smelt debt` lets an existing project adopt smelt incrementally: with
33
+ `findings.debt: .smelt/debt.json` in `smelt.yaml`, `smelt check` only fails on new violations, and SMT903 reports entries that
34
+ were fixed and can leave the file.
35
+
36
+ Exit codes: `0` clean, `1` violations at or above `--fail-on`, `2` config or usage error.
37
+
38
+ ## Configuration
39
+
40
+ `smelt.yaml` has one section per question:
41
+
42
+ | Section | Answers |
43
+ |---|---|
44
+ | `project` | Where is the code? (`source_roots`, `test_roots`; packages are discovered) |
45
+ | `architecture` | What shape should it have? (features, layers, shared, composition root, cross-feature relationships, cycles, roles) |
46
+ | `conventions` | How is code named, placed and tested? (`naming`, `packages`, `tests`) |
47
+ | `integrations` | Where does a framework get special treatment? (`dependency_injection`) |
48
+ | `analysis` | How does smelt read the code? (`imports`, `types`) |
49
+ | `rules` | How loud is a finding? Severities only, by rule name or code |
50
+ | `findings` | How are existing findings handled? (`debt`, `ignore`, `suppressions`) |
51
+
52
+ `smelt config show` prints the resolved config with all defaults; [docs/configuration.md](docs/configuration.md) explains the layout.
53
+
54
+ For a uv workspace, run `smelt init` at the workspace root. It reads
55
+ `tool.uv.workspace.members`, finds each member's source and test roots, and drafts one
56
+ configuration for all packages. Review the generated policy before adopting its findings:
57
+ feature/layer boundaries are inferred, not a declaration of your intended architecture.
58
+ For example, feature-local DI providers can be allowed to use the DI framework while
59
+ remaining in their original feature and layer:
60
+
61
+ ```yaml
62
+ architecture:
63
+ features: {root: backend.features}
64
+ composition_root: [backend.main, backend.lifespan]
65
+ cross_feature:
66
+ default: deny
67
+ allow:
68
+ - from: session.presentation
69
+ to: auth.presentation
70
+
71
+ integrations:
72
+ dependency_injection:
73
+ frameworks: [dishka] # always allowed in the composition root
74
+ allowed_in: ["backend.features.*.infrastructure.di"]
75
+ ```
76
+
77
+ The object form permits only that directional feature/layer relationship. The shorter
78
+ `"presentation -> presentation"` form remains available when a global layer-pair
79
+ exception is intended. `allowed_in` entries are dotted module patterns (`*` is one
80
+ segment, `**` any number) and also cover their submodules; unlike the composition root,
81
+ those modules keep all feature and layer rules.
82
+
83
+ For an architecture-first adoption, `smelt check --select SMT1,SMT3` focuses on
84
+ dependency and structure findings. To silence noisier testing rules persistently, use
85
+ severity overrides such as `rules: {private-access: off, interaction-assertion: off}` (rule names or codes) and review them later.
86
+
87
+ Silence a single finding inline, always with a reason:
88
+
89
+ ```python
90
+ from gateway.infra.sql import Repo # smelt: ignore[SMT101] -- migration tracked in #123
91
+ ```
92
+
93
+ With `conventions.tests.layout: mirror`, every test file must mirror a source module by its path:
94
+ `tests/billing/test_invoice.py` needs `app/billing/invoice.py`, and a package test
95
+ `tests/billing/test_billing.py` needs `app/billing/`. Not every module needs a test, but a
96
+ test whose source is missing or elsewhere is an error. Deliberately unmirrored tests go in
97
+ `conventions.tests.unmirrored` (e.g. `["tests/integration/**"]`);
98
+ `conventions.tests.mirror_suffixes: true` also allows `test_invoice_<topic>.py`.
99
+ `conventions.tests.mirror` sets the convention relative to the test
100
+ root: the default `{path}/test_{module}.py` drops the root package, `{root}/{path}/test_{module}.py`
101
+ keeps it, and `unit/{path}/{module}_test.py` puts tests under `tests/unit/` with a suffix.
102
+
103
+ Every rule has a page under [docs/rules](docs/rules/), and `smelt.schema.json` gives editors
104
+ autocompletion for `smelt.yaml`.
105
+
106
+ ## Python versions
107
+
108
+ Smelt runs on Python 3.12 to 3.14 and parses your code with the Python it runs on. Code
109
+ that uses newer syntax (3.14's `except A, B:` or t-strings, 3.13's type parameter defaults)
110
+ needs smelt on that version, e.g. `uvx -p 3.14 --from smelt-cli smelt check`; the syntax error says so when
111
+ `requires-python` or `.python-version` targets a newer Python.
112
+
113
+ ## Optional type information
114
+
115
+ Role detection is nominal by default: a class is an adapter when it inherits a port. With
116
+
117
+ ```yaml
118
+ analysis:
119
+ types: pyright # needs pyright on PATH; pyright_command overrides how it is run
120
+ ```
121
+
122
+ Smelt also asks pyright whether a class satisfies a port structurally, so a duck-typed
123
+ adapter is found too. It is never required: without it, every rule still runs.
124
+
125
+ ## Using Smelt with coding agents
126
+
127
+ Add this to your `AGENTS.md` or `CLAUDE.md`:
128
+
129
+ ```md
130
+ Before implementing, run `smelt context <feature>` to see where code belongs.
131
+ After every change, run `smelt check --changed --format json`.
132
+ Do not finish while errors remain. Use `smelt explain <code>` when unsure.
133
+ ```
134
+
135
+ Suggested loop: `smelt context <feature>` → edit → `smelt check --changed` → fix → tests →
136
+ pre-commit → CI (full check). Prefer the cheapest verification that gives sufficient
137
+ confidence: a rename needs smelt plus a type checker, new behavior needs one focused
138
+ regression test.
139
+
140
+ ## pre-commit
141
+
142
+ ```yaml
143
+ repos:
144
+ - repo: https://github.com/mathisarends/smelt
145
+ rev: v0.1.0
146
+ hooks:
147
+ - id: smelt
148
+ ```
149
+
150
+ ## GitHub Actions
151
+
152
+ CI always checks the whole repository, because cycles and transitive rules cannot be judged
153
+ from a diff alone.
154
+
155
+ ```yaml
156
+ - uses: astral-sh/setup-uv@v6
157
+ - run: uvx --from smelt-cli smelt check --format github
158
+ # optional: code scanning
159
+ - run: uvx --from smelt-cli smelt check --format sarif > smelt.sarif || true
160
+ - uses: github/codeql-action/upload-sarif@v3
161
+ with:
162
+ sarif_file: smelt.sarif
163
+ ```
164
+
165
+ ## Development
166
+
167
+ Requires [uv](https://docs.astral.sh/uv/).
168
+
169
+ ```bash
170
+ uv sync # create .venv and install dev dependencies
171
+ uv run pre-commit install
172
+ ```
173
+
174
+ Common commands:
175
+
176
+ ```bash
177
+ uv run pytest # run tests
178
+ uv run pytest --cov # run tests with coverage
179
+ uv run ruff check --fix . # lint
180
+ uv run ruff format . # format
181
+ uv run mypy # type-check
182
+ uv run pre-commit run --all-files # run all hooks
183
+ uv run smelt check # smelt checks itself
184
+ uv run python scripts/generate.py # refresh smelt.schema.json and docs/rules/
185
+ ```
186
+
187
+ Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/).
@@ -0,0 +1,117 @@
1
+ [project]
2
+ name = "smelt-cli"
3
+ version = "0.1.0"
4
+ description = "Static guardrails for Python codebases maintained by humans and coding agents."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "grimp>=3.17",
9
+ "pydantic>=2.13.5",
10
+ "pyyaml>=6.0.3",
11
+ ]
12
+
13
+ [[project.authors]]
14
+ name = "Mathis Arends"
15
+ email = "mathisarends27@gmail.com"
16
+
17
+ [project.scripts]
18
+ smelt = "smelt.cli:main"
19
+
20
+ [build-system]
21
+ requires = ["uv_build>=0.12.15,<0.13"]
22
+ build-backend = "uv_build"
23
+
24
+ [tool.uv.build-backend]
25
+ module-root = ""
26
+ module-name = "smelt"
27
+
28
+ [tool.ruff]
29
+ line-length = 88
30
+ target-version = "py312"
31
+ extend-exclude = [
32
+ "tests/fixtures",
33
+ "docs",
34
+ ]
35
+ force-exclude = true
36
+
37
+ [tool.ruff.lint]
38
+ select = [
39
+ "E",
40
+ "W",
41
+ "F",
42
+ "I",
43
+ "N",
44
+ "UP",
45
+ "B",
46
+ "A",
47
+ "C4",
48
+ "DTZ",
49
+ "T20",
50
+ "PT",
51
+ "RET",
52
+ "SIM",
53
+ "TC",
54
+ "PTH",
55
+ "ERA",
56
+ "PL",
57
+ "PERF",
58
+ "FURB",
59
+ "RUF",
60
+ "S",
61
+ ]
62
+ ignore = ["E501"]
63
+
64
+ [tool.ruff.lint.per-file-ignores]
65
+ "tests/**" = [
66
+ "S101",
67
+ "PLR2004",
68
+ ]
69
+
70
+ [tool.ruff.format]
71
+ docstring-code-format = true
72
+
73
+ [tool.mypy]
74
+ python_version = "3.12"
75
+ strict = true
76
+ warn_unreachable = true
77
+ pretty = true
78
+ files = [
79
+ "smelt",
80
+ "tests",
81
+ ]
82
+ exclude = ["^tests/fixtures/"]
83
+
84
+ [tool.pytest.ini_options]
85
+ minversion = "9.0"
86
+ testpaths = ["tests"]
87
+ norecursedirs = ["tests/fixtures"]
88
+ addopts = [
89
+ "-ra",
90
+ "--strict-markers",
91
+ "--strict-config",
92
+ ]
93
+ xfail_strict = true
94
+ filterwarnings = ["error"]
95
+
96
+ [tool.coverage.run]
97
+ source = ["smelt"]
98
+ branch = true
99
+
100
+ [tool.coverage.report]
101
+ show_missing = true
102
+ skip_covered = true
103
+ exclude_also = [
104
+ "if TYPE_CHECKING:",
105
+ "raise NotImplementedError",
106
+ "@overload",
107
+ ]
108
+
109
+ [dependency-groups]
110
+ dev = [
111
+ "mypy>=2.3",
112
+ "pre-commit>=4.6",
113
+ "pytest>=9.1",
114
+ "pytest-cov>=7.1",
115
+ "ruff>=0.16.8",
116
+ "types-pyyaml>=6.0.12.20260906",
117
+ ]
@@ -0,0 +1,116 @@
1
+ [project]
2
+ name = "smelt-cli"
3
+ version = "0.1.0"
4
+ description = "Static guardrails for Python codebases maintained by humans and coding agents."
5
+ readme = "README.md"
6
+ authors = [{ name = "Mathis Arends", email = "mathisarends27@gmail.com" }]
7
+ requires-python = ">=3.12"
8
+ dependencies = [
9
+ "grimp>=3.17",
10
+ "pydantic>=2.13.5",
11
+ "pyyaml>=6.0.3",
12
+ ]
13
+
14
+ [project.scripts]
15
+ smelt = "smelt.cli:main"
16
+
17
+ [build-system]
18
+ requires = ["uv_build>=0.12.15,<0.13"]
19
+ build-backend = "uv_build"
20
+
21
+ [tool.uv.build-backend]
22
+ module-root = ""
23
+ module-name = "smelt"
24
+
25
+ [dependency-groups]
26
+ dev = [
27
+ "mypy>=2.3",
28
+ "pre-commit>=4.6",
29
+ "pytest>=9.1",
30
+ "pytest-cov>=7.1",
31
+ "ruff>=0.16.8",
32
+ "types-pyyaml>=6.0.12.20260906",
33
+ ]
34
+
35
+ # --------------------------------------------------------------------------- #
36
+ # Ruff
37
+ # --------------------------------------------------------------------------- #
38
+ [tool.ruff]
39
+ line-length = 88
40
+ target-version = "py312"
41
+ # docs/ and smelt.schema.json are generated by scripts/generate.py.
42
+ extend-exclude = ["tests/fixtures", "docs"]
43
+ force-exclude = true
44
+
45
+ [tool.ruff.lint]
46
+ select = [
47
+ "E", # pycodestyle errors
48
+ "W", # pycodestyle warnings
49
+ "F", # pyflakes
50
+ "I", # isort
51
+ "N", # pep8-naming
52
+ "UP", # pyupgrade
53
+ "B", # flake8-bugbear
54
+ "A", # flake8-builtins
55
+ "C4", # flake8-comprehensions
56
+ "DTZ", # flake8-datetimez
57
+ "T20", # flake8-print
58
+ "PT", # flake8-pytest-style
59
+ "RET", # flake8-return
60
+ "SIM", # flake8-simplify
61
+ "TC", # flake8-type-checking
62
+ "PTH", # flake8-use-pathlib
63
+ "ERA", # eradicate (commented-out code)
64
+ "PL", # pylint
65
+ "PERF", # perflint
66
+ "FURB", # refurb
67
+ "RUF", # ruff-specific
68
+ "S", # flake8-bandit
69
+ ]
70
+ ignore = [
71
+ "E501", # line length is handled by the formatter
72
+ ]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ "tests/**" = [
76
+ "S101", # assert is fine in tests
77
+ "PLR2004", # magic values are fine in tests
78
+ ]
79
+
80
+ [tool.ruff.format]
81
+ docstring-code-format = true
82
+
83
+ # --------------------------------------------------------------------------- #
84
+ # Mypy
85
+ # --------------------------------------------------------------------------- #
86
+ [tool.mypy]
87
+ python_version = "3.12"
88
+ strict = true
89
+ warn_unreachable = true
90
+ pretty = true
91
+ files = ["smelt", "tests"]
92
+ exclude = ["^tests/fixtures/"]
93
+
94
+ # --------------------------------------------------------------------------- #
95
+ # Pytest / Coverage
96
+ # --------------------------------------------------------------------------- #
97
+ [tool.pytest.ini_options]
98
+ minversion = "9.0"
99
+ testpaths = ["tests"]
100
+ norecursedirs = ["tests/fixtures"]
101
+ addopts = ["-ra", "--strict-markers", "--strict-config"]
102
+ xfail_strict = true
103
+ filterwarnings = ["error"]
104
+
105
+ [tool.coverage.run]
106
+ source = ["smelt"]
107
+ branch = true
108
+
109
+ [tool.coverage.report]
110
+ show_missing = true
111
+ skip_covered = true
112
+ exclude_also = [
113
+ "if TYPE_CHECKING:",
114
+ "raise NotImplementedError",
115
+ "@overload",
116
+ ]
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("smelt-cli")
5
+ except PackageNotFoundError: # pragma: no cover
6
+ __version__ = "0.0.0"
@@ -0,0 +1,5 @@
1
+ import sys
2
+
3
+ from smelt.cli import main
4
+
5
+ sys.exit(main())
@@ -0,0 +1,27 @@
1
+ from smelt.analysis.context import AnalysisContext, ChangeSet, FileChange, Index
2
+ from smelt.analysis.files import FileIndex, SourceFile, TestFile
3
+ from smelt.analysis.imports import ImportDetail, ImportIndex, is_stdlib
4
+ from smelt.analysis.parsing import AnalysisError
5
+ from smelt.analysis.roles import RoleIndex
6
+ from smelt.analysis.syntax import ClassInfo, ModuleSyntax, SyntaxIndex
7
+ from smelt.analysis.types import PyrightTypes, TypeIndex
8
+
9
+ __all__ = [
10
+ "AnalysisContext",
11
+ "AnalysisError",
12
+ "ChangeSet",
13
+ "ClassInfo",
14
+ "FileChange",
15
+ "FileIndex",
16
+ "ImportDetail",
17
+ "ImportIndex",
18
+ "Index",
19
+ "ModuleSyntax",
20
+ "PyrightTypes",
21
+ "RoleIndex",
22
+ "SourceFile",
23
+ "SyntaxIndex",
24
+ "TestFile",
25
+ "TypeIndex",
26
+ "is_stdlib",
27
+ ]