Skip to content

Take the Auto-Fixable Ruff Findings, and Declare the Python Floor They Turn On - #644

Merged
ptr727 merged 3 commits into
developfrom
chore/ruff-autofixable-lint
Aug 9, 2026
Merged

Take the Auto-Fixable Ruff Findings, and Declare the Python Floor They Turn On#644
ptr727 merged 3 commits into
developfrom
chore/ruff-autofixable-lint

Conversation

@ptr727

@ptr727 ptr727 commented Aug 9, 2026

Copy link
Copy Markdown
Owner

The mechanically safe slice of the ruff backlog TODO.md recorded, plus the host floor that decides how much of that slice is safe.

Note on this description. It originally described the first commit, which declined UP017 on the ground that no python3 floor was declared. The maintainer then set the floor at 3.13, which reverses that decision, and the second commit takes the rewrite instead. The body below describes the change as it now stands. The superseded reasoning is kept in the commit history rather than here, since a description that argues for the opposite of its own diff is worse than one that is merely terse.

The ruff slice

This repository declares [tool.ruff] in pyproject.toml and spec/project-types.json declares python.ruff.config, yet no workflow runs ruff and the tree has never passed it. The full backlog is 106 findings plus a formatter that would rewrite 10449 lines across all 13 substantial Python files. This takes the auto-fixable findings only: import ordering, re.M spelled re.MULTILINE, two slice-to-removeprefix rewrites, an explicit f-string conversion, an unused noqa, a pair of extraneous parentheses, and UP017. The 70 findings needing judgment stay open, as does the formatter.

Each fix pass exposed the next, so the set was run to convergence rather than once. UP017 rewrote the call sites and left timezone imported but unused in three files, F401 removed those imports, and that left three import blocks unsorted for I001. A hand-picked rule set does not settle in one pass the way the full set does.

The python3 floor, at 3.13

spec/host-tools.json declared "minimum": null for python3, on the stated ground that no floor had been measured. That was not true of the tree: str.removeprefix and str.removesuffix are called in spec/audit.py and scripts/prose_lint.py on develop, which is a 3.9 requirement that had been there unmeasured and undeclared, and is exactly the gap that entry's own wording warns about.

The entry now declares 3.13 with the source block the schema requires once a floor exists. It is a target floor rather than a measured one, and the why says so outright: pyproject.toml sets ruff to py313 and mypy to 3.13, so the lint and type results this repo gates on describe 3.13 and describe no other interpreter, and a run below the floor is unverified rather than known broken. The two measured requirements are recorded beside it at 3.9 and 3.11, since those are what an older interpreter actually fails on, as an AttributeError at the call rather than at startup.

That distinction did not exist before this change, and four places stated the single-kind rule that a floor exists only where a version is known to break a documented procedure. All four now carry both kinds: the note in spec/host-tools.json, the minimum description in spec/host-tools.schema.json, docs/host-setup.md "Where a Tool Comes From, and How Old It May Be", and scripts/README.md. Sweeping by the term rather than by the instance is what found the last two.

The test that failed, which is the test working

scripts/test_host_gate.py asserted the floor set as exactly gh and git-restore-mtime, and it failed on this change. That assertion exists so adding a floor is a deliberate edit in the test rather than a silent one in the data, and it did that job. It now asserts the three-name set, and a second case asserts that a target floor identifies itself in its why, so a reader does not go hunting for a defect report that does not exist.

Verification

All 551 tests pass, spec/validate.py exits 0, scripts/host_gate.py exits 0 reporting python3 3.13.5 meets the 3.13 floor, and both the audit and write-guard self-tests report SELFTEST PASS. Every .py line ending is unchanged, checked by recording all 13 files' endings before and after and diffing the lists, since ruff rewrites in place and this repository pins endings per path.

prose_lint.py with the CI check list exits 0, repo_gate.py reports eol, eol-coverage and sha-pin clean, editorconfig-checker exits 0, and markdownlint-cli2 reports 0 issues across 44 files.

🤖 Generated with Claude Code

This repository declares [tool.ruff] and spec/project-types.json declares
python.ruff.config, yet no workflow runs ruff and the tree has never passed it.
This takes the mechanically safe slice only: 31 auto-fixable findings, import
ordering, re.M spelled re.MULTILINE, two slice-to-removeprefix rewrites, an
explicit f-string conversion, an unused noqa and a pair of extraneous parentheses.
The 70 findings needing judgment stay open, as does the formatter, which would
rewrite 10449 lines across all 13 substantial files.

UP017 is declined and the reason is recorded in pyproject.toml. It rewrites
timezone.utc to datetime.UTC, which needs Python 3.11, and spec/host-tools.json
declares no python3 floor on the stated ground that every script here is standard
library only. Taking that rewrite would create the floor silently, as a lint fix
rather than as a decision.

Applying it once before declining it also showed why the partial rule set matters:
the fix rewrote the call sites and left `timezone` imported but unused in three
files, which F401 then reported, since a rule set chosen by hand does not clean up
after itself the way the full set would.

All 550 unit tests pass, spec/validate.py, the audit selftest and the write-guard
selftest all pass, and every .py line ending is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 9, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR applies a mechanically safe subset of Ruff auto-fixes across the Python utilities in spec/, scripts/, and host-setup/, and documents the decision to ignore UP017 to avoid silently introducing a Python 3.11 runtime floor.

Changes:

  • Replace shorthand re flags (re.M, re.S, re.I, re.X) with their explicit equivalents (re.MULTILINE, re.DOTALL, re.IGNORECASE, re.VERBOSE) and make small regex-related cleanups.
  • Normalize/import-sort several Python files (split multi-import lines) and apply a few safe refactors (removeprefix/removesuffix, f-string formatting simplification, parentheses cleanup).
  • Add a documented Ruff ignore for UP017 in pyproject.toml to keep timezone.utc usage (avoid requiring Python 3.11’s datetime.UTC).

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
spec/validate.py Switch regex flag to re.MULTILINE for clarity/consistency.
spec/audit.py Regex flag constant expansions, removeprefix/removesuffix cleanup, and small formatting refactor.
scripts/test_repo_gate.py Split combined imports into one-per-line (Ruff/isort-friendly).
scripts/test_prose_lint.py Split imports and replace `re.M
scripts/test_pr_review.py Split combined imports into one-per-line.
scripts/test_host_gate.py Remove # noqa: E402 while still importing after a sys.path mutation (likely reintroduces E402).
scripts/repo_gate.py Split imports and change USES regex to use re.MULTILINE.
scripts/prose_lint.py Split combined imports into one-per-line.
scripts/pr_review.py Split combined imports and remove extraneous parentheses in a boolean expression.
scripts/host_gate.py Split combined imports into one-per-line.
pyproject.toml Add documented ignore = ["UP017"] under Ruff lint configuration.
host-setup/agent-safety/install.py Switch regex flag to re.DOTALL.
host-setup/agent-safety/gh-write-guard.py Replace shorthand regex flags with explicit constants (VERBOSE, DOTALL, IGNORECASE).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/test_host_gate.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (1)

spec/audit.py:715

  • str.removeprefix/removesuffix require Python 3.9+. This function previously worked on older Python 3.x, and spec/host-tools.json explicitly declares no python3 minimum, so this change can turn a previously-supported host interpreter into a runtime AttributeError during audits. Prefer the prior slice-based trimming to avoid silently raising the runtime floor here.
    s = s.removeprefix("|")
    s = s.removesuffix("|")

@ptr727

ptr727 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Answering the suppressed finding from the round on bd4d9d17, which carries no thread to resolve.

str.removeprefix/removesuffix require Python 3.9+. This function previously worked on older Python 3.x, and spec/host-tools.json explicitly declares no python3 minimum, so this change can turn a previously-supported host interpreter into a runtime AttributeError during audits.

Declined on the premise. The claim that the function previously worked on an older interpreter does not hold for the file it is about, because spec/audit.py already calls .removesuffix() on develop, before this branch touches it:

$ git grep -n "removeprefix\|removesuffix" origin/develop -- '*.py'
origin/develop:scripts/prose_lint.py:80:    `removeprefix` rather than `lstrip`, which takes a character set and ate the leading dot of
origin/develop:scripts/prose_lint.py:84:    return path.as_posix().removeprefix('./')
origin/develop:spec/audit.py:74:        return r.stdout.strip().rstrip("/").removesuffix(".git").split("/")[-1], True

spec/audit.py:74 is in the same module as the line under review, and it runs on the repository-name path every audit takes. An interpreter that cannot execute line 715 could not have executed line 74 either, so this change moves no floor. It makes an existing one more visible, which is a different thing and a better one.

The reasoning is the same one that declined UP017 in this pull request, applied consistently rather than by rule name. The test is whether the rewrite introduces a version requirement the tree does not already carry:

$ git grep -c "datetime.UTC\|from datetime import UTC" origin/develop -- '*.py'
(no output: zero occurrences)

datetime.UTC appears nowhere on develop, so taking UP017 would have created a 3.11 floor that did not exist. .removeprefix and .removesuffix are already the tree's baseline, so FURB188 creates nothing. One is declined and one is kept, on the measurement rather than on a preference about rewrites.

The finding does surface something real underneath it, and it is not this line. spec/host-tools.json declares "minimum": null for python3 on the stated ground that no floor has been measured. A floor of 3.9 has in fact existed in the tree for some time, unmeasured and undeclared, which is exactly the gap that entry's own wording warns about. Correcting a declared floor gates every host the fleet runs on, so it is a decision rather than a lint fix, and it is going to the maintainer separately rather than riding this pull request.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

The maintainer set the floor. spec/host-tools.json declared "minimum": null for
python3 on the ground that none had been measured, while the tree had in fact
required 3.9 for some time through str.removeprefix and str.removesuffix in
spec/audit.py and scripts/prose_lint.py. The entry now declares 3.13 with the
source block the schema requires once a floor exists.

It is a target floor rather than a measured one, and the why says so outright:
pyproject.toml sets ruff to py313 and mypy to 3.13, so the lint and type results
this repo gates on describe 3.13 and no other interpreter, and a run below the
floor is unverified rather than known broken. The two measured requirements are
recorded beside it, at 3.9 and 3.11, since those are what an older interpreter
actually fails on.

That distinction did not exist before, and four places stated the single-kind rule
that a floor exists only where a version is known to break a documented procedure.
All four now carry the two kinds: the note in spec/host-tools.json, the minimum
description in its schema, docs/host-setup.md "Where a Tool Comes From", and
scripts/README.md. Sweeping by the term rather than by the instance is what found
the last two.

With the floor declared, the reason UP017 was declined no longer holds, so the
ignore comes out and the rewrite goes in: datetime.UTC needs 3.11 and the floor is
above it. Removing the now-unused timezone imports left three import blocks
unsorted, so the fix set was run to convergence rather than once.

scripts/test_host_gate.py asserted the floor set as exactly gh and
git-restore-mtime, and it failed on this change, which is the assertion working. It
now asserts the three-name set and adds a case that a target floor says so in its
why, so a reader does not go looking for a defect report that does not exist.

All 551 tests pass, both selftests pass, spec/validate.py and scripts/host_gate.py
exit 0 with python3 3.13.5 meeting the floor, and every .py line ending is
unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 9, 2026 18:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (1)

spec/host-tools.json:53

  • The PR description says spec/host-tools.json currently has python3.minimum: null and that UP017 (timezone.utc -> datetime.UTC) was declined via an ignore+reasoning, but this change set does the opposite: it introduces a python3 floor (minimum: "3.13") and the tree now uses datetime.UTC/UTC (e.g. spec/audit.py, scripts/pr_review.py, scripts/test_pr_review.py). Also, the ruff config in pyproject.toml has no UP017 ignore (and doesn’t even mention UP rules). Please reconcile the PR narrative with the actual change (either update the PR description to match, or add the intended ignore+reasoning and revert the UTC changes / python floor if that was the goal).
            "name": "python3",
            "required": true,
            "probes": [["python3", "--version"], ["py", "-3", "--version"]],
            "pattern": "Python (\\d+(?:\\.\\d+)*)",
            "minimum": "3.13",
            "why": "Every script here is standard library only, so a bare interpreter is enough and no package floor exists. The floor is the toolchain target rather than a measured breakage one version below it, which is the one entry here that reads that way and says so rather than implying a defect nobody found. pyproject.toml sets ruff target-version to py313 and mypy python_version to 3.13, so the lint and type results this repo gates on describe 3.13 and describe no other interpreter, and a run below the floor is unverified rather than known broken. Two hard requirements are measured and both sit lower: str.removeprefix and str.removesuffix need 3.9 and are called in spec/audit.py and scripts/prose_lint.py, and datetime.UTC needs 3.11 and is used in spec/audit.py, scripts/pr_review.py and their tests. Those are what an older interpreter actually fails on, as an AttributeError at the call rather than at startup. The name rather than the version is what differs per platform, which the second probe covers.",

@ptr727 ptr727 changed the title Take the Auto-Fixable Ruff Findings, and Say Why One Is Declined Take the Auto-Fixable Ruff Findings, and Declare the Python Floor They Turn On Aug 9, 2026
@ptr727

ptr727 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Answering the suppressed finding from the round on 77aaa58c, which carries no thread to resolve.

The PR description says spec/host-tools.json currently has python3.minimum: null and that UP017 was declined via an ignore+reasoning, but this change set does the opposite [...] Please reconcile the PR narrative with the actual change.

Accepted in full, and the description is rewritten. The narrative described the first commit, and the second commit reversed it: the maintainer set the python3 floor at 3.13, which removed the ground the UP017 decline stood on, so the ignore came out and the rewrite went in. Every specific in the finding is correct, including that pyproject.toml no longer mentions UP at all, since the four-line reasoning block was deleted along with the ignore entry it justified.

This is the more useful kind of finding, because nothing in the diff is wrong and no test could have caught it. A pull request description is read as the account of record for why a change looks the way it does, and one arguing for the opposite of its own diff sends a reviewer looking for a mistake in the code that is not there.

The description now leads with a short note that it was rewritten and why, rather than being silently replaced. The superseded reasoning is worth finding rather than erasing, and it is in the commit history where it belongs: bd4d9d1 declines UP017 and states the ground, and 77aaa58 declares the floor and records that the ground no longer holds.

One correction to the finding's framing, since it matters for how the floor reads. The change does not raise the runtime floor to 3.13 in the sense of making 3.13 features load-bearing. Nothing in the tree needs 3.13. The measured requirements are 3.9 for str.removeprefix and str.removesuffix, which develop already carried, and 3.11 for datetime.UTC, which this change adds. 3.13 is declared as a target floor, naming the version the repo's own toolchain is configured for, and spec/host-tools.json now distinguishes that from a measured floor rather than leaving a reader to assume a defect was found at 3.12.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (1)

spec/host-tools.json:53

  • The python3 floor rationale says "the lint and type results this repo gates on" describe 3.13, but this repo's CI workflow currently doesn't run ruff/mypy (e.g. .github/workflows/validate-task.yml runs only the stdlib script tests). This reads like an enforced CI gate when it's really a toolchain target/config choice, so the wording is misleading for hosts/users troubleshooting a floor failure.
            "why": "Every script here is standard library only, so a bare interpreter is enough and no package floor exists. The floor is the toolchain target rather than a measured breakage one version below it, which is the one entry here that reads that way and says so rather than implying a defect nobody found. pyproject.toml sets ruff target-version to py313 and mypy python_version to 3.13, so the lint and type results this repo gates on describe 3.13 and describe no other interpreter, and a run below the floor is unverified rather than known broken. Two hard requirements are measured and both sit lower: str.removeprefix and str.removesuffix need 3.9 and are called in spec/audit.py and scripts/prose_lint.py, and datetime.UTC needs 3.11 and is used in spec/audit.py, scripts/pr_review.py and their tests. Those are what an older interpreter actually fails on, as an AttributeError at the call rather than at startup. The name rather than the version is what differs per platform, which the second probe covers.",

The rationale said the lint and type results this repo "gates on" describe 3.13.
It gates on neither. No workflow runs ruff, mypy or pyright, and validate-task.yml
runs the standard-library script tests, the prose gate and the repo gate and
nothing else.

The wording mattered where it is read. A host that fails this floor goes looking
for the CI failure the sentence implied, finds none, and is left unable to tell
whether the floor is real. It now says what those tools report describes 3.13,
that neither runs in CI, and that a host failing the floor therefore has no CI
failure to point at.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 9, 2026 18:57
@ptr727

ptr727 commented Aug 9, 2026

Copy link
Copy Markdown
Owner Author

Answering the second suppressed finding from the round on 77aaa58c. Fixed in dfc525c.

The python3 floor rationale says "the lint and type results this repo gates on" describe 3.13, but this repo's CI workflow currently doesn't run ruff/mypy [...] This reads like an enforced CI gate when it's really a toolchain target/config choice, so the wording is misleading for hosts/users troubleshooting a floor failure.

Accepted. The claim is false and I had already measured that it was false earlier in this same pull request, where establishing that no workflow runs ruff is part of why the ruff backlog exists at all. Writing "gates on" afterwards contradicted my own measurement.

Re-checked rather than taken on memory:

$ grep -rn "ruff\|mypy\|pyright" .github/workflows/
(no matches)

validate-task.yml runs spec/validate.py, the four script test suites, the audit and write-guard self-tests, repo_gate.py, and prose_lint.py twice. No linter or type checker for Python appears anywhere in CI.

The finding's reasoning about where this is read is the part worth keeping. why is what host_gate.py prints to a host that fails the floor, so it is read by someone troubleshooting rather than by someone reviewing. Told the results are gated on, they go looking for a CI failure, find none, and cannot tell whether the floor is real or stale. That is a worse outcome than terse wording.

It now says what those tools report describes 3.13, that neither runs in CI, and that a host failing this floor therefore has no CI failure to point at. That last clause is the one the finding earns, since it answers the question the reader actually has.

This also sharpens the measured-versus-target distinction this pull request introduces. A measured floor is backed by an observed defect, a target floor by a configuration choice, and only the first gives a host something to point at. The python3 entry now says which of the two it is in both senses.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 9eb2510 into develop Aug 9, 2026
7 checks passed
@ptr727
ptr727 deleted the chore/ruff-autofixable-lint branch August 9, 2026 19:03
ptr727 added a commit that referenced this pull request Aug 22, 2026
#920)

The repo's Python floor is 3.13 (`spec/host-tools.json`'s `python3`
target, `pyproject.toml`'s
`target-version`/`python_version`), but that wasn't visible from every
place a reader could hit a
lower number first, which invites review comments that treat ordinary
3.10+ syntax (`X | None`,
etc.) as needing pre-3.10 hedging.

- `repo-config/configure.sh`: its `resolve_description.py` probe accepts
3.7+ (PEP 563), which is
real but was worded as if it were this repo's floor rather than one
script's parse minimum.
Reworded the comment and the failure message to name 3.13 as the actual
floor.
- `spec/host-tools.json`: cross-referenced `configure.sh` and the
`skills_install.sh` /
`install-skills.*` bootstrap scripts from the `python3` entry's `why`,
so a reader who lands on
either script's 3.7 language finds the real floor next to it instead of
a second number.
- `python-codestyle` skill
(`.agents/skills/python-codestyle/references/code-style.md`, the
hand-authored source; `.github/skills/` and
`.claude-plugin/fleet-skills/` regenerated via
`scripts/build_dist.py`): the Type hints section already says to use
modern syntax, but didn't
say a 3.13 floor is why no hedging is needed. Added that, naming the two
deliberate bootstrap
  exceptions.
- `.github/copilot-instructions.md`: deleted a `Disproved Claims` entry
that had gone stale. It
was proved against `install.py`'s interpreter floor at 3.7 (`dbd1cdc`);
#644 raised that floor to
3.11 without updating this entry, so it now asserts something about
`install.py` that isn't true
anymore. Its own governing rule says a stale-subject entry is deleted
rather than patched to look
  current.

Verified locally: `scripts/prose_lint.py` clean, `scripts/build_dist.py
--check` clean,
`scripts/repo_gate.py` clean, `pytest scripts/tests/test_host_gate.py
scripts/tests/test_prose_lint.py scripts/tests/test_repo_gate.py` all
pass (392 passed).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Clarified Python version support, including Python 3.13 as the primary
target and Python 3.7+ compatibility for selected bootstrap scripts.
- Updated type-hinting guidance to explain when compatibility
annotations are required.
- Expanded setup and host-tool documentation around interpreter
requirements.
  - Removed outdated, disproven guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants