Skip to content

fix(tooling): close the gap between npm run lint:py and the CI Python lint job #2694

Description

Summary

npm run lint:py runs ruff check only. The CI job runs ruff check and ruff format --check. A locally green Python change can therefore still fail CI. This actually happened during PR #2599 and cost a build cycle plus two incorrect "validation green" reports.

Correcting the original framing

This was first recorded as "unpinned ruff>=0.15 means a formatter release can break CI with no code change." That is wrong, and re-verification on main shows why:

  • .github/workflows/python-lint.yml:62 installs with uv sync --locked.
  • Every skill's uv.lock pins an exact ruff version, for example powerpoint 0.15.4, demo-video 0.15.20, adr-author 0.15.12.
  • Dependabot already covers the uv ecosystem weekly.

CI is deterministic. The lockfile is authoritative. Nobody should spend time "fixing" a determinism problem that does not exist.

The actual defect

npm run lint:py CI Python Lint job
ruff check
ruff format --check

scripts/linting/Invoke-PythonLint.ps1 documents this deliberately: it defaults to read-only ruff check for CI gating, and applies ruff format only under -Fix. The intent is reasonable — the default lane should not mutate source. The problem is that the local lane's name and output imply coverage it does not have.

Options

  1. Add a non-mutating ruff format --check to the default lint:py path so it matches the CI job. Most direct; keeps the lane read-only.
  2. Add a separate lint:py:format script and reference it from the contributing docs.
  3. Document the divergence only, and leave the lanes as they are.

Option 1 seems right — a local lint lane that does not reproduce its CI counterpart is a trap — but the maintainer should decide, since it may be a deliberate command-taxonomy choice under .github/instructions/ci-owned-validation.instructions.md. Read that before changing package.json.

Secondary: ruff version drift

Separate and lower value. Ruff has drifted across 11 skills, so formatting behaviour is not uniform repository-wide even though each skill is individually reproducible:

Version Skills
0.15.4 powerpoint
0.15.6 jira, gitlab
0.15.10 tts-voiceover
0.15.11 customer-card-render
0.15.12 mural, adr-author
0.15.15 vally-tests
0.15.17 accessibility
0.15.20 demo-video, vex

Also note project-planning/adr-author declares ruff>=0.6 in pyproject.toml while every other skill declares >=0.15. Worth aligning the declared floor even if the locks stay independent.

Converging these is optional. Do not let a version bump silently reformat source as a side effect; if a newer ruff wants formatting changes, surface that as its own decision.

Local Ruff resolution parity

The local runner currently resolves Ruff from each project's .venv, then falls back to a global Ruff on PATH. CI instead runs uv sync --locked followed by uv run ruff, so the local and CI commands can use different Ruff versions and produce different formatter verdicts. This is observable today: some projects use the global fallback, and an unsynchronized environment can cross the Ruff 0.15/0.16 boundary where Markdown code blocks enter formatter scope.

Resolve this as part of this issue rather than a separate follow-up. Reuse the lock-bound uv execution pattern already used by the local Python test runner, while retaining a clear and tested fallback when uv is unavailable. Account explicitly for the large scripts/evals/moderation environment so the fix does not introduce an unexplained 935 MB sync cost into the ordinary lint path.

Acceptance Criteria

  • A local command reproduces the CI Python lint job's pass/fail verdict, or the divergence is explicitly documented where contributors will see it.
  • ci-owned-validation.instructions.md was consulted before changing any package.json script.
  • No source is reformatted as an incidental side effect of this change.
  • npm run validate:skills and the dependency-pinning validation pass.
  • Local Ruff execution is lock-bound for projects with uv.lock, using the project's locked Ruff version rather than an arbitrary .venv or global binary.
  • Behavior when uv is unavailable is explicit, documented, and covered by tests.
  • The ordinary lint path handles scripts/evals/moderation without an unexplained full-environment sync cost.

Verified against main at 278eb128.

Metadata

Metadata

Labels

enhancementNew feature or requestlintingLinting rules and validationneeds-triageRequires triage and prioritizationpriority-3Medium priority, standard queuetoolingDeveloper tooling and utilities

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions