test-privata 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 (32) hide show
  1. test_privata-0.1.0/.claude/settings.local.json +10 -0
  2. test_privata-0.1.0/.github/workflows/ci.yml +51 -0
  3. test_privata-0.1.0/.github/workflows/docs.yml +54 -0
  4. test_privata-0.1.0/.github/workflows/release.yml +24 -0
  5. test_privata-0.1.0/.gitignore +13 -0
  6. test_privata-0.1.0/.pre-commit-config.yaml +40 -0
  7. test_privata-0.1.0/.pre-commit-hooks.yaml +16 -0
  8. test_privata-0.1.0/AGENTS.md +23 -0
  9. test_privata-0.1.0/LICENSE +21 -0
  10. test_privata-0.1.0/PKG-INFO +175 -0
  11. test_privata-0.1.0/README.md +142 -0
  12. test_privata-0.1.0/docs/assets/logo.svg +9 -0
  13. test_privata-0.1.0/docs/contributing.md +58 -0
  14. test_privata-0.1.0/docs/getting-started.md +92 -0
  15. test_privata-0.1.0/docs/index.md +46 -0
  16. test_privata-0.1.0/docs/usage.md +110 -0
  17. test_privata-0.1.0/pyproject.toml +114 -0
  18. test_privata-0.1.0/src/privata/__init__.py +37 -0
  19. test_privata-0.1.0/src/privata/__main__.py +5 -0
  20. test_privata-0.1.0/src/privata/_checker.py +162 -0
  21. test_privata-0.1.0/src/privata/_entrypoints.py +87 -0
  22. test_privata-0.1.0/src/privata/_exports.py +170 -0
  23. test_privata-0.1.0/src/privata/_imports.py +265 -0
  24. test_privata-0.1.0/src/privata/_models.py +76 -0
  25. test_privata-0.1.0/src/privata/_modules.py +317 -0
  26. test_privata-0.1.0/src/privata/_source_roots.py +81 -0
  27. test_privata-0.1.0/src/privata/_version.py +24 -0
  28. test_privata-0.1.0/src/privata/cli.py +38 -0
  29. test_privata-0.1.0/tests/__init__.py +1 -0
  30. test_privata-0.1.0/tests/test_checker.py +1633 -0
  31. test_privata-0.1.0/uv.lock +742 -0
  32. test_privata-0.1.0/zensical.toml +101 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(Get-ChildItem -Recurse -Force)",
5
+ "Bash(Select-Object -First 100)",
6
+ "Bash(Format-Table FullName)",
7
+ "Bash(uv run *)"
8
+ ]
9
+ }
10
+ }
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, macos-latest]
16
+ python-version: ["3.12", "3.13"]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v7
23
+
24
+ - name: Set up Python ${{ matrix.python-version }}
25
+ run: uv python install ${{ matrix.python-version }}
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --all-extras --dev
29
+
30
+ - name: Run tests
31
+ run: uv run pytest
32
+
33
+ - name: Build package
34
+ run: uv build
35
+
36
+ lint:
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - uses: actions/checkout@v6
40
+
41
+ - name: Install uv
42
+ uses: astral-sh/setup-uv@v7
43
+
44
+ - name: Set up Python
45
+ run: uv python install 3.12
46
+
47
+ - name: Install dependencies
48
+ run: uv sync --all-extras --dev
49
+
50
+ - name: Run pre-commit (via prek)
51
+ uses: j178/prek-action@v1
@@ -0,0 +1,54 @@
1
+ name: Documentation
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ concurrency:
15
+ group: "pages"
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ build:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout repository
23
+ uses: actions/checkout@v6
24
+
25
+ - name: Set up Python
26
+ uses: actions/setup-python@v6
27
+ with:
28
+ python-version: "3.12"
29
+
30
+ - name: Install uv
31
+ uses: astral-sh/setup-uv@v7
32
+
33
+ - name: Install dependencies
34
+ run: uv sync --group docs
35
+
36
+ - name: Build documentation
37
+ run: uv run zensical build
38
+
39
+ - name: Upload artifact
40
+ uses: actions/upload-pages-artifact@v4
41
+ with:
42
+ path: ./site
43
+
44
+ deploy:
45
+ if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
46
+ environment:
47
+ name: github-pages
48
+ url: ${{ steps.deployment.outputs.page_url }}
49
+ runs-on: ubuntu-latest
50
+ needs: build
51
+ steps:
52
+ - name: Deploy to GitHub Pages
53
+ id: deployment
54
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,24 @@
1
+ name: Upload Python Package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ deploy:
9
+ runs-on: ubuntu-latest
10
+ environment:
11
+ name: pypi
12
+ url: https://pypi.org/p/test-privata
13
+ permissions:
14
+ id-token: write
15
+ steps:
16
+ - uses: actions/checkout@v6
17
+ with:
18
+ fetch-depth: 0
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v7
21
+ - name: Build
22
+ run: uv build
23
+ - name: Publish package distributions to PyPI
24
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,13 @@
1
+ .DS_Store
2
+ .venv/
3
+ .ruff_cache/
4
+ .pytest_cache/
5
+ .coverage
6
+ __pycache__/
7
+ *.py[cod]
8
+ dist/
9
+ build/
10
+ site/
11
+ *.egg-info/
12
+ .privata/
13
+ src/privata/_version.py
@@ -0,0 +1,40 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+ - id: check-merge-conflict
10
+ - id: debug-statements
11
+
12
+ - repo: https://github.com/astral-sh/ruff-pre-commit
13
+ rev: v0.14.9
14
+ hooks:
15
+ - id: ruff-check
16
+ args: [--fix]
17
+ - id: ruff-format
18
+
19
+ - repo: local
20
+ hooks:
21
+ - id: privata
22
+ name: privata (module privacy)
23
+ entry: uv run privata .
24
+ language: system
25
+ pass_filenames: false
26
+ types_or: [python, pyi]
27
+
28
+ - id: mypy
29
+ name: mypy (type checker)
30
+ entry: uv run mypy src tests
31
+ language: system
32
+ types: [python]
33
+ pass_filenames: false
34
+
35
+ - id: ty
36
+ name: ty (type checker)
37
+ entry: uv run ty check
38
+ language: system
39
+ types: [python]
40
+ pass_filenames: false
@@ -0,0 +1,16 @@
1
+ - id: privata
2
+ name: privata
3
+ description: Check that Python module privacy boundaries stay intentional.
4
+ entry: privata .
5
+ language: python
6
+ pass_filenames: false
7
+ types_or: [python, pyi]
8
+
9
+ - id: privata-manual
10
+ name: privata
11
+ description: Check Python module privacy boundaries on demand.
12
+ entry: privata .
13
+ language: python
14
+ pass_filenames: false
15
+ stages: [manual]
16
+ types_or: [python, pyi]
@@ -0,0 +1,23 @@
1
+ # Repository Guidelines
2
+
3
+ Privata is a small Python package for AST-based module privacy checks.
4
+
5
+ ## Development
6
+
7
+ - Use `uv sync --extra dev --group docs` for a full development environment.
8
+ - Run `uv run pytest` for tests.
9
+ - Run `uv run ruff check .`, `uv run ruff format --check .`, `uv run mypy src tests`, and `uv run ty check` before submitting changes.
10
+ - Use `uv build` to verify packaging.
11
+
12
+ ## Structure
13
+
14
+ - `src/privata/_checker.py` contains the checker implementation.
15
+ - `src/privata/cli.py` exposes the console script.
16
+ - `tests/test_checker.py` contains focused behavior tests.
17
+ - `docs/` contains the Zensical documentation site.
18
+
19
+ ## Style
20
+
21
+ - Keep runtime dependencies minimal.
22
+ - Prefer standard-library parsing and typed data structures.
23
+ - Preserve the rule that test imports do not count when deciding whether a symbol should remain public.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bas Nijholt
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.
@@ -0,0 +1,175 @@
1
+ Metadata-Version: 2.4
2
+ Name: test-privata
3
+ Version: 0.1.0
4
+ Summary: A Python module privacy checker for keeping public interfaces intentional.
5
+ Project-URL: Repository, https://github.com/ColinKennedy/privata
6
+ Author: Colin Kennedy
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Keywords: architecture,code-quality,imports,module-boundaries,python,static-analysis
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.12
25
+ Provides-Extra: dev
26
+ Requires-Dist: mypy>=1.14; extra == 'dev'
27
+ Requires-Dist: pre-commit>=4; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=7.1; extra == 'dev'
29
+ Requires-Dist: pytest>=8.4; extra == 'dev'
30
+ Requires-Dist: ruff>=0.13; extra == 'dev'
31
+ Requires-Dist: ty; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # Privata
35
+
36
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
37
+ [![CI](https://github.com/basnijholt/privata/actions/workflows/ci.yml/badge.svg)](https://github.com/basnijholt/privata/actions/workflows/ci.yml)
38
+ [![PyPI](https://img.shields.io/pypi/v/privata.svg)](https://pypi.org/project/privata/)
39
+ [![Python Versions](https://img.shields.io/pypi/pyversions/privata.svg)](https://pypi.org/project/privata/)
40
+ [![Docs](https://img.shields.io/badge/docs-privata.nijho.lt-blue)](http://privata.nijho.lt/)
41
+
42
+ <img src="docs/assets/logo.svg" alt="Privata logo" align="right" width="120" />
43
+
44
+ Find Python code that looks public but is only used privately.
45
+
46
+ Privata is a static checker for keeping module boundaries intentional.
47
+ It scans your production Python modules and reports four kinds of interface drift:
48
+
49
+ - public top-level functions, classes, variables, and type aliases that are only used inside their own module
50
+ - imports of private modules such as `pkg._internal` from outside their owning package subtree
51
+ - imports of private top-level symbols such as `pkg.service._Helper` from another production module
52
+ - literal `__all__` declarations that are stale, incomplete, or exporting names that do not exist
53
+
54
+ It is designed for packages and applications where `helper()` should become `_helper()` once it is no longer part of the production interface.
55
+ Test imports do not count, so tests can still reach internals without forcing those internals to stay public.
56
+
57
+ ## Example
58
+
59
+ Given:
60
+
61
+ ```python
62
+ # src/example/service.py
63
+ def helper() -> int:
64
+ return 1
65
+
66
+
67
+ def run() -> int:
68
+ return helper()
69
+ ```
70
+
71
+ Privata reports:
72
+
73
+ ```text
74
+ Found 1 public symbols that could be made private:
75
+
76
+ src/example/service.py:1: function `helper`
77
+ ```
78
+
79
+ ## Install
80
+
81
+ ```bash
82
+ uv tool install privata
83
+ ```
84
+
85
+ For local development:
86
+
87
+ ```bash
88
+ uv sync --extra dev --group docs
89
+ uv run pre-commit install
90
+ ```
91
+
92
+ ## Usage
93
+
94
+ Run Privata from a project root:
95
+
96
+ ```bash
97
+ privata .
98
+ ```
99
+
100
+ Privata uses `tach.toml` `source_roots` when present.
101
+ Otherwise it prefers `src/` when that directory exists, and falls back to scanning the project root while ignoring tests, virtualenvs, build output, docs output, and hidden tooling directories.
102
+
103
+ Use Privata as a pre-commit hook in another repository:
104
+
105
+ ```yaml
106
+ repos:
107
+ - repo: https://github.com/basnijholt/privata
108
+ rev: v0.1.2
109
+ hooks:
110
+ - id: privata
111
+ ```
112
+
113
+ For a less strict setup that only runs when requested:
114
+
115
+ ```yaml
116
+ repos:
117
+ - repo: https://github.com/basnijholt/privata
118
+ rev: v0.1.2
119
+ hooks:
120
+ - id: privata-manual
121
+ ```
122
+
123
+ ```bash
124
+ pre-commit run --hook-stage manual privata-manual --all-files
125
+ ```
126
+
127
+ Full output can include multiple issue types:
128
+
129
+ ```text
130
+ Found 2 public symbols that could be made private:
131
+
132
+ src/example/service.py:12: function `helper`
133
+ src/example/service.py:21: class `InternalState`
134
+
135
+ Found 1 private module imports outside their package subtree:
136
+
137
+ src/example/api.py:3: imports private module `example.worker._runtime`
138
+
139
+ Found 1 private symbol imports from production modules:
140
+
141
+ src/example/api.py:4: imports private symbol `example.worker.runtime._Helper`
142
+
143
+ Found 1 __all__ export issues:
144
+
145
+ src/example/__init__.py:5: public name `Service` missing from __all__
146
+ ```
147
+
148
+ If the project is clean:
149
+
150
+ ```text
151
+ No module privacy issues found.
152
+ ```
153
+
154
+ ## What Privata Checks
155
+
156
+ - Public top-level functions, classes, variables, and type aliases in production source roots.
157
+ - Whether those symbols are imported by another production module under those roots.
158
+ - Whether private modules such as `pkg._internal` are imported outside their containing package subtree.
159
+ - Whether private top-level symbols are imported from another production module.
160
+ - Whether literal `__all__` declarations exactly match public top-level bindings.
161
+ - Console entry points in `pyproject.toml`.
162
+ - Uvicorn entry points in shell scripts and Dockerfiles.
163
+ - Symbols exported through package `__init__.py` and `__all__`.
164
+ - Tach `[[interfaces]]` entries, when `tach.toml` is present.
165
+
166
+ Privata intentionally ignores imports from `tests/`.
167
+ If only tests import a symbol, Privata treats that symbol as private.
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ uv run pytest # enforces 100% coverage
173
+ uv run pre-commit run --all-files
174
+ uv build
175
+ ```
@@ -0,0 +1,142 @@
1
+ # Privata
2
+
3
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![CI](https://github.com/basnijholt/privata/actions/workflows/ci.yml/badge.svg)](https://github.com/basnijholt/privata/actions/workflows/ci.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/privata.svg)](https://pypi.org/project/privata/)
6
+ [![Python Versions](https://img.shields.io/pypi/pyversions/privata.svg)](https://pypi.org/project/privata/)
7
+ [![Docs](https://img.shields.io/badge/docs-privata.nijho.lt-blue)](http://privata.nijho.lt/)
8
+
9
+ <img src="docs/assets/logo.svg" alt="Privata logo" align="right" width="120" />
10
+
11
+ Find Python code that looks public but is only used privately.
12
+
13
+ Privata is a static checker for keeping module boundaries intentional.
14
+ It scans your production Python modules and reports four kinds of interface drift:
15
+
16
+ - public top-level functions, classes, variables, and type aliases that are only used inside their own module
17
+ - imports of private modules such as `pkg._internal` from outside their owning package subtree
18
+ - imports of private top-level symbols such as `pkg.service._Helper` from another production module
19
+ - literal `__all__` declarations that are stale, incomplete, or exporting names that do not exist
20
+
21
+ It is designed for packages and applications where `helper()` should become `_helper()` once it is no longer part of the production interface.
22
+ Test imports do not count, so tests can still reach internals without forcing those internals to stay public.
23
+
24
+ ## Example
25
+
26
+ Given:
27
+
28
+ ```python
29
+ # src/example/service.py
30
+ def helper() -> int:
31
+ return 1
32
+
33
+
34
+ def run() -> int:
35
+ return helper()
36
+ ```
37
+
38
+ Privata reports:
39
+
40
+ ```text
41
+ Found 1 public symbols that could be made private:
42
+
43
+ src/example/service.py:1: function `helper`
44
+ ```
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ uv tool install privata
50
+ ```
51
+
52
+ For local development:
53
+
54
+ ```bash
55
+ uv sync --extra dev --group docs
56
+ uv run pre-commit install
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ Run Privata from a project root:
62
+
63
+ ```bash
64
+ privata .
65
+ ```
66
+
67
+ Privata uses `tach.toml` `source_roots` when present.
68
+ Otherwise it prefers `src/` when that directory exists, and falls back to scanning the project root while ignoring tests, virtualenvs, build output, docs output, and hidden tooling directories.
69
+
70
+ Use Privata as a pre-commit hook in another repository:
71
+
72
+ ```yaml
73
+ repos:
74
+ - repo: https://github.com/basnijholt/privata
75
+ rev: v0.1.2
76
+ hooks:
77
+ - id: privata
78
+ ```
79
+
80
+ For a less strict setup that only runs when requested:
81
+
82
+ ```yaml
83
+ repos:
84
+ - repo: https://github.com/basnijholt/privata
85
+ rev: v0.1.2
86
+ hooks:
87
+ - id: privata-manual
88
+ ```
89
+
90
+ ```bash
91
+ pre-commit run --hook-stage manual privata-manual --all-files
92
+ ```
93
+
94
+ Full output can include multiple issue types:
95
+
96
+ ```text
97
+ Found 2 public symbols that could be made private:
98
+
99
+ src/example/service.py:12: function `helper`
100
+ src/example/service.py:21: class `InternalState`
101
+
102
+ Found 1 private module imports outside their package subtree:
103
+
104
+ src/example/api.py:3: imports private module `example.worker._runtime`
105
+
106
+ Found 1 private symbol imports from production modules:
107
+
108
+ src/example/api.py:4: imports private symbol `example.worker.runtime._Helper`
109
+
110
+ Found 1 __all__ export issues:
111
+
112
+ src/example/__init__.py:5: public name `Service` missing from __all__
113
+ ```
114
+
115
+ If the project is clean:
116
+
117
+ ```text
118
+ No module privacy issues found.
119
+ ```
120
+
121
+ ## What Privata Checks
122
+
123
+ - Public top-level functions, classes, variables, and type aliases in production source roots.
124
+ - Whether those symbols are imported by another production module under those roots.
125
+ - Whether private modules such as `pkg._internal` are imported outside their containing package subtree.
126
+ - Whether private top-level symbols are imported from another production module.
127
+ - Whether literal `__all__` declarations exactly match public top-level bindings.
128
+ - Console entry points in `pyproject.toml`.
129
+ - Uvicorn entry points in shell scripts and Dockerfiles.
130
+ - Symbols exported through package `__init__.py` and `__all__`.
131
+ - Tach `[[interfaces]]` entries, when `tach.toml` is present.
132
+
133
+ Privata intentionally ignores imports from `tests/`.
134
+ If only tests import a symbol, Privata treats that symbol as private.
135
+
136
+ ## Development
137
+
138
+ ```bash
139
+ uv run pytest # enforces 100% coverage
140
+ uv run pre-commit run --all-files
141
+ uv build
142
+ ```
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="title desc">
2
+ <title id="title">Privata logo</title>
3
+ <desc id="desc">The private-module mark: bracket, dot, underscore, bracket.</desc>
4
+ <rect width="128" height="128" rx="28" fill="#101828"/>
5
+ <path d="M48 36H32v56h16" fill="none" stroke="#cbd5e1" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
6
+ <path d="M80 36h16v56H80" fill="none" stroke="#cbd5e1" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <circle cx="54" cy="72" r="6" fill="#f8fafc"/>
8
+ <rect x="66" y="76" width="18" height="8" rx="4" fill="#22d3ee"/>
9
+ </svg>
@@ -0,0 +1,58 @@
1
+ ---
2
+ icon: lucide/git-pull-request
3
+ ---
4
+
5
+ # Contributing
6
+
7
+ Contributions are welcome.
8
+
9
+ ## Development Setup
10
+
11
+ ```bash
12
+ git clone https://github.com/basnijholt/privata.git
13
+ cd privata
14
+ uv sync --extra dev --group docs
15
+ uv run pre-commit install
16
+ ```
17
+
18
+ ## Run Tests
19
+
20
+ ```bash
21
+ uv run pytest # enforces 100% coverage
22
+ ```
23
+
24
+ ## Code Quality
25
+
26
+ ```bash
27
+ uv run pre-commit run --all-files
28
+ uv build
29
+ ```
30
+
31
+ Install the Git hook once after setup:
32
+
33
+ ```bash
34
+ uv run pre-commit install
35
+ ```
36
+
37
+ ## Build Docs
38
+
39
+ ```bash
40
+ uv run zensical build
41
+ ```
42
+
43
+ The generated site is written to `site/`.
44
+
45
+ ## Project Structure
46
+
47
+ ```text
48
+ src/privata/
49
+ ├── __init__.py Public package API
50
+ ├── __main__.py python -m privata entry point
51
+ ├── _checker.py AST-based privacy analysis
52
+ └── cli.py Console script wrapper
53
+ ```
54
+
55
+ ## Release
56
+
57
+ Releases are published from GitHub Releases through trusted publishing.
58
+ Create a release tag such as `v0.1.0`; the `release.yml` workflow builds and uploads to PyPI.