Skip to content

Meta linter in typed python; pyright gate (#220 stage 2, #256) - #258

Merged
wallstop merged 7 commits into
masterfrom
agent/session-090-meta-lint-python-pyright
Oct 14, 2026
Merged

wallstop merged 7 commits into
masterfrom
agent/session-090-meta-lint-python-pyright

Conversation

@wallstop

@wallstop wallstop commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

DISCLOSURE: LLM-GENERATED TEXT

Why:
The meta linter was the last Unity-free gate still on PowerShell and the only one with no test suite; landing it in python brings the typed-python surface to two linters, which is the trigger #256 set for the typing gate.

What:

  • lint_unity_meta.py ports lint-unity-meta.ps1 (orphan metas, missing metas, guid uniqueness over git ls-files); the ps1 entry becomes a delegator so the pre-commit hook and the Unity Meta Lint workflow keep their command.
  • test_lint_unity_meta.py adds the meta linter's first suite: 24 red-green cases over the three rule classes, the folder-asset rule, the Unity-ignoration exemptions, BOM/CRLF/non-UTF-8/exotic-separator reads, non-UTF-8 path names, git discovery, and the CLI surface.
  • pyright 1.1.414 (basic mode) type-checks the two linters and their suites (chore: Type-check the python tooling with a pinned pyright step #256); the pin lives in pyproject.toml's non-default typing group, so uv run - every delegator's path - never installs it, and CI requests it explicitly with uv's package cache keyed on the manifest.
  • The Unity Meta Lint workflow pins python 3.12 + uv like the llm lint job and now runs both OS legs; the llm lint trigger widens to tooling~/**/*.py so the gate sees every python edit.
  • The preflight lane test's fixed 300ms overlap window (failed on a loaded Windows runner) becomes a bounded 5s start-marker poll; mutation-proven to still fail a serialized run.
  • create-unity-meta skill and context.md name the new implementation and the gate.

How we know:
The old ps1 and the port print identical reports on this repo (1407 paths checked, sorted-diff empty) and agree on adversarial fixtures (uppercase keys, exotic separators, invalid bytes); both python suites green (24 + 9) under uv; pyright 0 errors; preflight 19/19. Three adversarial review rounds drove the parity fixes (re.IGNORECASE, errors=replace, the ReadAllLines split), the decode hardening, and the flake rewrite.

Closes #256. Part of #220 (stage 2).


Note

Low Risk
Changes are confined to tooling scripts, CI, and docs; linter behavior is parity-tested against the former PowerShell implementation with no runtime or auth impact.

Overview
Ports the Unity .meta hygiene linter from PowerShell to typed Python (lint_unity_meta.py, issue #220 stage 2). lint-unity-meta.ps1 is now a thin uv/python delegator so pre-commit and CI keep the same entry points; test_lint_unity_meta.py (plus test-lint-unity-meta.ps1) adds the first contract tests and runs in the LLM instructions workflow.

Adds the #256 typing gate: pinned pyright in a non-default typing dependency group, pyrightconfig.json, and a Linux-only CI step (uv run --group typing pyright) with uv cache; workflow path filters widen to tooling~/**/*.py. The Unity Meta Lint job mirrors the LLM job (Python 3.12, uv) and runs on ubuntu + windows.

lint_skill_sizes.py gains a Stream protocol for cleaner annotations. Docs (context.md, create-unity-meta skill, pre-commit description) name the Python implementation and pyright gate. Preflight’s concurrent-check test drops a flaky 300ms window for a 5s sibling-start poll with per-child marker files.

Reviewed by Cursor Bugbot for commit 832297e. Bugbot is set up for automated code reviews on this repo. Configure here.

lint_unity_meta.py is the implementation; lint-unity-meta.ps1 becomes a
delegator so the pre-commit hook and the Unity Meta Lint workflow keep
their command. The port adds the meta linter's first test suite
(19 red-green cases over the three rule classes, the folder-asset rule,
the Unity-ignoration exemptions, and the CLI surface); the old ps1 and
the port print identical reports on this repo (1403 paths checked).
pyright 1.1.414 (basic mode) type-checks the two typed-python linters
and their suites. The pin lives in pyproject.toml's non-default typing
group, so plain uv run - every delegator's path - never installs it;
the CI step requests it and caches uv's packages keyed on the manifest.
Two linters now live in python, the gate #256 asked for is landed.
Also pins the interpreter the Unity Meta Lint workflow runs on and
widens the llm lint trigger to tooling~/**/*.py so the gate sees every
python edit.
The review caught two undisclosed rule divergences in the port: the
ps1's -match family was case-insensitive (the port now keeps that with
re.IGNORECASE - a GUID:/folderAsset: YES meta parses like the ps1) and
.NET's ReadAllLines replaced invalid bytes instead of throwing (the
port now decodes with errors=replace, so a stray latin-1 byte cannot
move a file into the guid bucket). Verified old-ps1 vs port on
fixtures for both. The suite gains the BOM/CRLF, non-UTF-8, non-ASCII
path, and middle-segment pins and asserts exit 0 on the passing
folder-asset branches.

The report stream seam is typed (a Stream protocol) in both linters,
sweeping stage 1's getattr, and the ps1 docstrings claim python 3.11+
(the manifest floor) instead of 3.9+. The Unity Meta Lint workflow
runs both OS legs so the delegator's Windows wiring is enforced in CI.

Also rewrites the preflight lane test whose fixed 300ms overlap window
failed on a loaded windows runner (spawn lag past the window while the
checks really overlapped): the lead check now polls up to 5s for the
follower's start, and a serialized run still fails the pin.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread tooling~/scripts/tests/preflight.test.mjs Outdated
Comment thread tooling~/scripts/tests/test_lint_unity_meta.py Outdated
Round-2 review: str.splitlines also split \x0b-\x1e and the U+2028/2029
family, so a folderAsset line holding a vertical tab read as two lines
and the port passed where the ps1 errored. The split is now exactly
.NET's CR/LF/CRLF set and both of the review's fixtures agree with the
old ps1. Two deltas stay and are stated in the module docstring with
their direction: python's Unicode casefold (a 'guıd:' key parses where
the invariant culture did not) and python's \s matching \x1c-\x1f.
git_tracked_paths also maps a missing repo root or missing git to the
ps1's controlled failure message (was a raw traceback), with a test.
Round-3 review: .NET's ReadAllLines BOM-sniffed UTF-16/32, which the
port does not - the docstring now names that as a third read limit
instead of claiming full decode parity; a UTF-16 meta fails closed as
an unparsable guid. And a tracked filename whose bytes are not UTF-8
printed surrogates that crashed the report with a UnicodeEncodeError
traceback after its first line: the CLI now reconfigures the console
streams to backslashreplace, so the report survives the way the ps1's
lossy print did, pinned by a git-backed CLI test.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 02e76ca. Configure here.

Comment thread tooling~/scripts/tests/test_lint_unity_meta.py
os.fsdecode is strict UTF-8 on Windows (PEP 529), so the fixture raise
before the CLI ran and the windows llm-lint leg went red. A byte-invalid
tracked name cannot exist on an NTFS checkout anyway: the pinned
console behavior is the POSIX checkout's, and the test says so.
The mergeWrite marker was one file both children rewrote, so a
follower write landing between the lead's read and write could erase
the sawSibling record the assert reads (found adjudicating the Bugbot
finding round 5 carried). Each child now owns its marker file: no
read-modify-write, no lost update; the mutation check still fails a
serialized run. Children that crash fail the assert with their output
instead of costing a silent 5s poll, and the markers are absolute so
nothing leaks into the repo root (the first draft leaked one).

The BOM fixture's re-write dropped the BOM it existed to pin (the
utf-8 write-back stripped it); the bytes are now written once with
utf-8-sig and the fixture asserts the BOM is really there.
@wallstop
wallstop merged commit 6d26fc9 into master Oct 14, 2026
12 checks passed
@wallstop
wallstop deleted the agent/session-090-meta-lint-python-pyright branch October 14, 2026 17:16
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.

chore: Type-check the python tooling with a pinned pyright step

1 participant