-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathnoxfile.py
More file actions
192 lines (156 loc) Β· 6.82 KB
/
noxfile.py
File metadata and controls
192 lines (156 loc) Β· 6.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# SPDX-FileCopyrightText: 2026 PythonWoods <dev@pythonwoods.dev>
# SPDX-License-Identifier: Apache-2.0
import os
import nox
nox.options.reuse_existing_virtualenvs = True
nox.options.default_venv_backend = "uv"
# Sessions run by plain `nox` (fast feedback loop, no side effects).
# For the full CI-equivalent pipeline use: nox -s preflight
# NOTE: posargs are forwarded with `--`, e.g.: nox -s lint -- --fix
nox.options.sessions = ["lint", "format", "typecheck"]
PYTHONS = ["3.11", "3.12", "3.13"]
# Per-group sync tuples β each session installs only what it needs.
_SYNC_TEST = ("uv", "sync", "--active", "--group", "test")
_SYNC_LINT = ("uv", "sync", "--active", "--group", "lint")
_SYNC_RELEASE = ("uv", "sync", "--active", "--group", "release")
_SYNC_DEV = ("uv", "sync", "--active", "--group", "dev")
@nox.session(python=PYTHONS)
def tests(session: nox.Session) -> None:
"""Run the test suite with branch coverage across all supported Python versions."""
session.run(*_SYNC_TEST, external=True)
session.run(
"pytest",
"--cov=src/zenzic",
"--cov-report=term-missing",
"--cov-report=xml:coverage.xml",
*session.posargs,
env={"HYPOTHESIS_PROFILE": os.environ.get("HYPOTHESIS_PROFILE", "ci")},
)
@nox.session(python="3.11")
def lint(session: nox.Session) -> None:
"""Run ruff linting checks.
Read-only by default (used in CI). To auto-fix: nox -s lint -- --fix
"""
session.run(*_SYNC_LINT, external=True)
session.run("ruff", "check", *session.posargs, "src/", "tests/")
@nox.session(python="3.11")
def format(session: nox.Session) -> None: # noqa: A001
"""Check code formatting with ruff (read-only, used in CI)."""
session.run(*_SYNC_LINT, external=True)
session.run("ruff", "format", "--check", "src/", "tests/")
@nox.session(python="3.11")
def fmt(session: nox.Session) -> None:
"""Auto-format code with ruff in place (use during development)."""
session.run(*_SYNC_LINT, external=True)
session.run("ruff", "format", "src/", "tests/")
@nox.session(python="3.11")
def typecheck(session: nox.Session) -> None:
"""Run static type checking with mypy."""
session.run(*_SYNC_LINT, external=True)
session.run("mypy", "src/")
@nox.session(python="3.11")
def reuse(session: nox.Session) -> None:
"""Verify REUSE/SPDX license compliance."""
session.run(*_SYNC_LINT, external=True)
session.run("reuse", "lint")
@nox.session(python="3.11")
def security(session: nox.Session) -> None:
"""Audit third-party dependencies for known CVEs with pip-audit."""
session.install("pip-audit")
req = os.path.join(session.create_tmp(), "requirements.txt")
session.run(
"uv",
"export",
"--no-emit-project",
"--frozen",
"--no-hashes",
"-o",
req,
external=True,
)
session.run(
"pip-audit",
"--strict",
"-r",
req,
# CVE-2026-4539: ReDoS in Pygments AdlLexer (archetype.py).
# Attack vector is LOCAL-only (crafted .adl file); Zenzic does not
# process ADL input and uses Pygments only for documentation syntax
# highlighting. No patched release exists on PyPI yet.
# Remove this exemption once pygments>=2.19.3 (or equivalent) ships.
"--ignore-vuln",
"CVE-2026-4539",
)
@nox.session(python="3.11")
def mutation(session: nox.Session) -> None:
"""Run mutation testing with mutmut on the security-critical core modules.
Targets (configured in ``[tool.mutmut]`` in ``pyproject.toml``):
- ``src/zenzic/core/rules.py`` β rule engine and regex canary
- ``src/zenzic/core/shield.py`` β secret detection (ZRT-001/ZRT-003)
- ``src/zenzic/core/reporter.py`` β _obfuscate_secret() masking function
A surviving mutant means a test gap. Goal: mutation score β₯ 90%.
Implementation note β non-editable install:
``uv sync`` installs zenzic as an editable package whose ``.pth`` file
points Python directly to the original ``src/`` tree. This bypasses
mutmut's mutation injection, which modifies a *copy* of the source
files inside ``mutants/``. The ``uv pip install --no-editable`` step
below switches to a static install so that the mutations are visible to
pytest during each test run. The sync step is still needed first to
resolve and install all transitive test dependencies.
"""
session.run(*_SYNC_TEST, external=True)
# Reinstall as non-editable so that mutmut's source injection is visible
# to pytest (editable .pth files would bypass the mutated copy in mutants/).
# Note: 'uv pip install .' (without --editable) installs the built wheel,
# which is non-editable by default.
session.run("uv", "pip", "install", ".", external=True)
session.run(
"mutmut",
"run",
*session.posargs,
)
session.run("mutmut", "results")
@nox.session(python="3.11")
def preflight(session: nox.Session) -> None:
"""Run all quality checks β equivalent to a full CI pipeline."""
session.run(*_SYNC_DEV, external=True)
session.run("ruff", "check", "src/", "tests/")
session.run("ruff", "format", "--check", "src/", "tests/")
session.run("mypy", "src/")
session.run(
"pytest",
"--cov=src/zenzic",
"--cov-report=term-missing",
"--cov-report=xml:coverage.xml",
)
session.run("reuse", "lint")
# Pillar 1: Self-check β Zenzic validates its own integrity.
# --exit-zero: The Core repo has docs_dir = "." which scans prose files
# containing example secrets and code snippets from the CHANGELOG.
# These findings are expected and not actionable. The documentation
# site (zenzic-doc) runs the strict self-check without this flag.
session.run("zenzic", "check", "all", "--exit-zero")
@nox.session(python=False, venv_backend="none")
def dev(session: nox.Session) -> None:
"""One-time developer setup: install pre-commit hooks.
Run once after cloning:
nox -s dev
"""
session.run("uv", "sync", "--group", "dev", external=True)
session.run("uv", "run", "pre-commit", "install", external=True)
@nox.session(python="3.11", venv_backend="none")
def bump(session: nox.Session) -> None:
"""Bump the project version and create a release commit + tag.
Usage:
nox -s bump -- patch # 0.1.0 β 0.1.1
nox -s bump -- minor # 0.1.0 β 0.2.0
nox -s bump -- major # 0.1.0 β 1.0.0
After bumping, push with:
git push && git push --tags
"""
if not session.posargs:
session.error("Specify a bump type: nox -s bump -- patch|minor|major")
part = session.posargs[0]
if part not in ("patch", "minor", "major"):
session.error(f"Invalid bump type '{part}'. Use patch, minor, or major.")
session.run("bump-my-version", "bump", part, external=True)