Conversation
Applies the get2knowio Python Project Standard (toolchain, gates, and release
shape) to Hangar. The bulk of the source diff is a one-time `ruff format` pass;
the substantive changes are below.
Toolchain and packaging (backend/pyproject.toml):
- Derive the version from git tags via hatch-vcs. This closes live drift: the
manifest said 0.3.11 while src/hangar/__init__.py said 0.1.0. hatch-vcs needs
`raw-options = { root = ".." }` because it does not search parent directories
for .git in a monorepo.
- Move dev tooling into PEP 735 [dependency-groups]; optional-dependencies now
holds only the real `postgres` extra a consumer would install.
- Add [tool.uv] exclude-newer, the resolver-side twin of the Dependabot cooldown.
- Bound requires-python at >=3.12,<3.15 so the range makes no untested claim.
Gates:
- New root mise.toml defines the seven canonical verbs. CI now runs `mise run
setup` / `mise run check` and spells out no gate inline, so what CI checks and
what a contributor runs cannot drift.
- `ruff format --check` is now gated; it previously was not. Ruff moves to the
standard explicit `select` list and stops ignoring E501.
- pytest gains --strict-markers, --strict-config and --timeout=30.
- CI exercises both ends of requires-python (3.12 and 3.14). The floor had never
been run. The version-free `Backend` job remains the required status check and
gates on the matrix, so an interpreter bump never invalidates branch
protection.
- Coverage floor set to 80, the minimum measured across the matrix rather than
the best leg: the suite covers 87% on 3.14 but 80% on 3.12.
Supply chain and release:
- Add top-level permissions: and concurrency: to every workflow (was 1 of 5).
- Dependabot now groups production minor/patch as well as development.
- release.yml gains a test job gating the publish and a GitHub Release with
generated notes. Pre-release detection now matches both PEP 440 canonical and
dashed forms; the previous substring test would have published v1.0.0rc1 as
:latest.
Also fixed along the way:
- ruff's C416 rewrite of a SQLAlchemy row comprehension to dict() dropped row
typing; .tuples() satisfies both the lint and type gates.
- `uv sync` silently reuses an existing .venv regardless of the mise pin, so a
matrix leg could claim to test 3.12 while testing whatever was already there.
backend:setup now binds --python to the resolved interpreter.
- The devcontainer pinned Node 22 while CI, mise and the Dockerfile used 26.
- The Dockerfile builds the wheel in a discarded stage that has .git, so the
runtime image gets a tag-derived version without shipping git history.
tests/unit/test_toolchain_consistency.py enforces the interpreter-agreement rule,
the version-free required-check names, and the dependency-group split as tests
rather than comments — a principle that isn't a test is a suggestion.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLmETZdERJ6RFCdwZRdv4a
The 7-point coverage difference between 3.12 (80%) and 3.14 (87%) was a
measurement artifact, not a behavioural one. Root cause, in order:
1. `branch = true` was set in the coverage config.
2. coverage's sys.monitoring core ("sysmon") does not support branch coverage
before Python 3.14, so on 3.12 coverage silently fell back to the older
sys.settrace core.
3. sys.settrace does not see code executing in the worker thread Starlette's
TestClient drives the app from, so every FastAPI BackgroundTasks job was
invisible to it.
services/sync.py is reached almost entirely through a BackgroundTask (the resync
queued by PUT /providers/{id}/repos), which is why the gap concentrated there.
Verified rather than inferred: on a single interpreter (3.14) the core alone
moves sync.py between 32% (ctrace), 32% (pytrace) and 46% (sysmon), and a probe
wrapping SyncService.sync_connection shows it starting and completing
identically on both interpreters. The code always ran; only the measurement
differed.
Fix: turn branch coverage off so sysmon is usable on 3.12, and pin
COVERAGE_CORE=sysmon from mise.toml (coverage reads the core from the
environment only, never from pyproject.toml). Both legs now report an identical
89.90% and the same 338 uncovered statements, so one floor is meaningful
everywhere.
The floor moves 80 -> 89. The previous 80 was not a real measurement of this
suite; it was the number the blinded core produced.
CONTRIBUTING.md gains a Coverage section covering the footgun this leaves
behind: a bare `pytest --cov` on 3.12 still measures with the wrong core and
reports a false failure, so coverage must be run through `mise run`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLmETZdERJ6RFCdwZRdv4a
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Applies the get2knowio Python Project Standard — toolchain, gates, and release shape — to Hangar, along with the build-surface and hatch-vcs standards it depends on.
Reviewing tip: 68 of the 85 changed files are a one-time
ruff formatpass.git diff -wor reviewing everything exceptbackend/src/**andbackend/tests/**gets you the substance.Verified
All four gates green on both matrix legs — 310 tests, format/lint/types clean, coverage floor met on 3.12 and 3.14. The image builds and reports a tag-derived version with no git history in the runtime layer.
What changed
Toolchain & packaging —
hatch-vcsversioning, dev tooling moved to PEP 735[dependency-groups],[tool.uv] exclude-newer,requires-pythonbounded at>=3.12,<3.15.Build surface — new root
mise.tomlwith the seven canonical verbs (mise tasks, no justfile). CI now runsmise run setup/mise run checkand spells out no gate inline.ruff format --checkis gated for the first time; ruff moves to the standard explicitselectlist and stops ignoringE501.CI — exercises both ends of
requires-python. The version-freeBackendjob stays the required status check and gates on the matrix, so an interpreter bump never invalidates branch protection.Supply chain & release —
permissions:+concurrency:on 5/5 workflows (was 1/5); Dependabot groups production minor/patch too;release.ymlgains a test gate and a GitHub Release with generated notes.Bugs found while applying it
pyproject.tomlsaid0.3.11,src/hangar/__init__.pysaid0.1.0. Exactly the class hatch-vcs closes.uv syncsilently reuses an existing.venvregardless of the mise pin — my first "3.12" run was actually 3.14.backend:setupnow binds--pythonto the resolved interpreter.contains(ref_name, '-')test would have publishedv1.0.0rc1as:latest. Now matches both PEP 440 canonical and dashed forms.dict()dropped row typing;.tuples()satisfies both gates.Decisions worth a second opinion
Coverage floor is 89, and the 3.12/3.14 gap turned out to be a measurement bug (fixed in
a145d2f). The suite originally reported 87% on 3.14 but 80% on 3.12. That was not a behavioural difference:branch = truewas set in the coverage config.sysmoncore doesn't support branch coverage before Python 3.14, so 3.12 silently fell back to the oldersys.settracecore.sys.settracecan't see code running in the worker thread Starlette'sTestClientdrives the app from — so every FastAPIBackgroundTasksjob was invisible to it.services/sync.pyis reached almost entirely through a BackgroundTask (the resync queued byPUT /providers/{id}/repos), which is why the gap concentrated there.Verified rather than inferred: on a single interpreter (3.14) the core alone moves
sync.pybetween 32% (ctrace), 32% (pytrace) and 46% (sysmon); and a probe wrappingSyncService.sync_connectionshows it starting and completing identically on both interpreters. The code always ran — only the measurement differed.Fixed by turning branch coverage off (so
sysmonis usable on 3.12) and pinningCOVERAGE_CORE=sysmonfrommise.toml. Both legs now report an identical 89.90% and the same 338 uncovered statements. The floor is 89 — the earlier 80 was never a real measurement of this suite, just what the blinded core produced.Tradeoff worth a look: this gives up branch coverage to get one trustworthy number on every leg. The alternative is keeping branch coverage and enforcing the floor only on the 3.14 leg. Happy to switch if you'd rather keep branch data.
Two claims in the standard don't match the repo. Starship is installed (
modern-cli-toolswith{}installs everything by default). And Hangar isn't in the hatch-vcs rollout list, though the rule table does cover it — I applied it because the drift was live.Not done
devcontainer-lock.json— needs the devcontainer CLI, which isn't available in the container; the file is resolved digests I won't fabricate. Rundevcontainer upgrade --workspace-folder ..api-types.ts. Needs a.prettierignorefirst, and it's outside a Python standard.frontend/package.jsonstill carries a hand-bumped0.3.11, now inconsistent with the backend's derived version.The devcontainer changes (base image →
ubuntu-24.04, mise feature, Node 26,MISE_TRUSTED_CONFIG_PATHS) need a container rebuild to take effect.🤖 Generated with Claude Code
https://claude.ai/code/session_01VLmETZdERJ6RFCdwZRdv4a