Skip to content

chore: Migrate all scripts (js, ps1, bash, batch, etc) to typed python via uv #171

Open

Description

@wallstop

Every script-like thing in our repo should be typed python via uv. Reading js, ps1, etc is quite difficult and it doesn't make sense to have all of these different surfaces.

Where it makes sense to keep some things, like node/package/npm commands, this is ok, but ideally, as MUCH of the "non-unity" code in this repo moves to typed python + uv as possible.

Activity

  1. wallstop commented on Oct 11, 2026

    @wallstop
    OwnerAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    First slice delivered in PR #172: the harness surface is now a typed Python project executed through uv.

    What landed:

    • pyproject.toml + uv.lock at the repo root: zero runtime dependencies, requires-python >= 3.9, pyright strict configuration; scripts/gate.py passes strict pyright.
    • scripts/gate.py runs the whole non-Unity review ladder (CSharpier, Prettier, member-order/null-assertions/usings, assembly policy, skills index + drift, .llm instructions + file lengths, release credentials, line endings, the full harness self-test suite, npm pack dry run) as six parallel lanes. It only orchestrates the repository's existing checks; no check logic moved.
    • Per-step wall times on the console, --report <path> machine-readable JSON (schema 1: environment + per-step command/seconds/exit), --plan dry plan, --only <step> failed-step re-run with dependencies. Wired as npm run check:gate; self-tested in scripts/tests/test-gate.ps1 through --plan (interpreter-only, no toolchain needed); harness-scope.psm1 maps the new surface so CI keeps running the scoped selection.
    • The self-test suite runs as its own phase after the quick lanes: measured all-concurrent vs phased on this bind-mounted checkout, the suite saturating the machine made every other lane 3-4x slower.
    • CI untouched: no workflow changes, same scoped self-test selection, same checks. The local gate now also runs the release-credential and line-ending checks the old ladder only ran in CI, and drops the redundant post-format check steps locally (CI still runs them; a successful format already proves the same invariant).

    Measured on this checkout (Linux container, 12 vCPU, bind-mounted working tree): the quick lanes run in ~8-16 s concurrently vs ~25-30 s serial; the harness self-test suite remains the long pole (37-105 s depending on host noise) and is unchanged by this PR. The gate's value so far is one-command ergonomics, the two added checks, and the measurement artifact rather than a large end-to-end wall reduction here.

    Proposed migration order for the rest of this issue (one bounded slice per PR, each carrying its self-test port or mapping so coverage never drops):

    1. Fast-check trio: fast-check.ps1 + harness-scope.psm1 + should-run-self-tests.ps1 -> one typed Python module pair; port test-fast-check.ps1 / test-harness-scope.ps1. This is the agent hot path and removes the pwsh startup tax from every iteration. CI adoption (uv setup on both runners + timing check) belongs to this slice because CI shares should-run-self-tests.ps1 and run-all.ps1.
    2. Docs build: build-docs.ps1 hand-rolls python -m venv .docs-venv + requirements-docs.txt (mkdocs, mkdocs-material); uv manages that environment natively.
    3. Lint scripts: the six node lints (lint-csharp-member-order, lint-csharp-null-assertions, lint-csharp-usings, lint-changelog-length, lint-line-endings, lint-release-secrets) and the four PowerShell lints, one PR per script with its self-test ported.
    4. Release pipeline: scripts/release/*.mjs (six scripts) and their five self-test files last — the most battle-tested surface, migrated only after the pattern has proven itself on 1-3.
    5. Self-test runner: run-all.ps1 once its subjects are Python (a PowerShell runner for Python tests is the last thing worth keeping).

    Constraints carried forward: pwsh stays required until slice 4 is done (CI and run-unity-validation.ps1 still need it), and node stays for npm lifecycle per this issue's carve-out. The Unity-side harness (run-unity-validation.ps1) is out of scope unless the owner wants it migrated too.

  2. wallstop commented on Oct 11, 2026

    @wallstop
    OwnerAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    Second slice delivered in PR #173: the fast-check trio is now typed Python.

    What landed:

    • scripts/fast_check.py, scripts/harness_scope.py, and scripts/should_run_self_tests.py (stdlib-only, pyright strict, launched through uv) replace fast-check.ps1, harness-scope.psm1, and should-run-self-tests.ps1. Verdicts and fail-safe selection semantics are identical, and the plan became a JSON contract (--plan, schema 1) like the gate's.
    • Scheduling is the change: the two per-call git scans run concurrently (each measured ~1.1 s on the bind-mounted checkout, sequential before), and the selected check groups run as a dependency DAG instead of sequential blocks; self-tests stay a second phase so the suite keeps full throughput.
    • CI (llm-lint.yml) resolves the self-test scope with the system Python interpreter (python3/python fallback) instead of two pwsh launches — same scoped selection, no uv bootstrap, coverage unchanged. CI uv adoption stays deferred: a setup step would cost more per job than it saves for a stdlib-only script.
    • The trio's self-tests (test-fast-check.ps1, test-harness-scope.ps1) now drive the Python entry points, every original case kept plus a mapped-subject plan case and a base-SHA contract pin; the scope map, context, and forbidden-pattern references moved to the new names.

    Measured (same-window, interleaved, N=7): plan floor on the bind-mounted checkout 2.8 s → 2.1 s via uv; whole-iteration A/B in a fresh local clone: scripts-only change 3.9 s → 2.9 s (-26%), scripts + C# + packaging change 5.6 s → 3.0 s (-47%).

    One defect found during validation: importing the scope module wrote scripts/__pycache__/*.pyc, whose untracked surface paths fail-safed the next scan into the full suite — a self-inflicted full-suite loop that would also have broken CI's scoped skip. The entry scripts now suppress bytecode writes and .gitignore ignores the directory.

    Remaining for this issue: the suite runner (run-all.ps1), the .js lint scripts, the release pipeline .mjs files, the remaining .ps1 helpers, and the docs build.

  3. wallstop commented on Oct 11, 2026

    @wallstop
    OwnerAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    Third slice delivered in PR #174: the .llm lint trio and the self-test runner are typed Python.

    What landed:

    • scripts/llm_config.py + scripts/generate_skills_index.py + scripts/lint_llm_instructions.py + scripts/lint_file_lengths.py (stdlib-only, pyright strict) replace LlmConfig.psm1, generate-skills-index.ps1, lint-llm-instructions.ps1, and lint-file-lengths.ps1. Verdicts, exit codes, message shapes, and flags are identical; the generated index is byte-identical apart from its banner, which now names the Python generator.
    • scripts/tests/run_all.py replaces run-all.ps1 as the suite runner: same discovered test-*.ps1 set, same concurrent child-pwsh execution with output replayed in name order, same --names fail-safe selection, same summary lines. The parent pwsh is gone from the hot path; the test files themselves remain pwsh (the remaining surface for this issue).
    • The gate and the fast check now derive their Python launcher from the host: uv run where uv is installed, the running interpreter where it is not. The first CI run caught this: the plan hardcoded uv, and test-gate.ps1's resolve-every-program check failed on uv-less runners — the plan now only describes commands that execute on the host that built it.
    • CI lint job: one "Resolve the Python interpreter" step validates the interpreter actually executes (the Windows python3 App-Execution-Alias stub answers command -v but is not a working interpreter; a garbage scope verdict would silently run the full suite on every Windows job), the three lint steps run through it, and the checkout is shallow since the scope step already fetches a missing base.
    • The trio's and the runner's self-tests drive the Python entries; every original case kept.

    Measured (same-window interleaved, both sides on the same filesystem): harness lane per-step 0.82-1.33 s (pwsh) → 0.12-0.23 s (Python), lane total 3.3-3.6 s → 0.5-0.6 s (-80-85%); the self-test suite itself is unchanged (44.8/38.0 vs 45.1/49.1 s, load variance). A parallelism-cap experiment (throttle 4) lost to interleaved A/B (uncapped 31-41 s vs capped 51-56 s on 12 vCPU, parity on 4 pinned CPUs) and was reverted before landing — the spawn-bound suite really does want every child started immediately.

    One more defect caught by CI: test-opencode-service-recycle lost its fake service once under the 28-way concurrent suite (ubuntu job) — recorded as #175 with log evidence; 25/25 serial green locally the same day, and the rerun on this PR passed.

    Remaining for this issue: the six node lints, the release .mjs pipeline, build-docs.ps1, and the assembly/Unity-validation pwsh lints.

  4. wallstop commented on Oct 11, 2026

    @wallstop
    OwnerAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    Fourth slice delivered in PR #176: the assembly warning-policy lint is typed Python, which retires the last PowerShell lint from the agent path.

    What landed:

    • scripts/lint_assembly_warnings.py (stdlib-only, pyright strict) replaces scripts/lint-assembly-warnings.ps1. Verdicts, exit codes, message shapes, and the root-flag behavior are identical, including the case-sensitivity split the original encoded: response-file name checks stay case-sensitive (-ceq), every -match pattern stays case-insensitive, and hidden entries stay excluded from the recursive scan the way Get-ChildItem -Recurse excluded them.
    • test-assembly-warnings.ps1 now drives the Python entry through Find-Python; all 25 original cases kept, one file still, same assertions.
    • Callers repointed: the gate's assembly lane, the fast check's assembly_config group, the pre-commit hook, npm run lint:assembly-warnings, and the lint CI step (which now runs through the resolved ${PYTHON}). The retired path keeps its harness-scope entry so a deletion still selects its tests.
    • One process-behavior nuance preserved deliberately: the scan walks the same tree the PowerShell version walked (including node_modules), so verdict parity beats scan shortcuts.

    Measured (same-window interleaved, min of 5 on this checkout): the lane command itself 4.25 s (pwsh) → 1.00 s (Python), 4.3x; the gate's assembly lane 7.4 s → 2.4 s across full gate runs, so markdown formatting is now the quick-phase long pole. On CI, the ubuntu lint job's pwsh step measured 4 s (run 38160432095) against roughly 1 s for every node/Python lint step; the Windows job measured 1 s. Lint CI needs PowerShell for no lint step after this; the self-test files remain pwsh, which is the remaining surface for this issue.

    Remaining for this issue: the six node lints, the release-pipeline .mjs files, the docs build, run-unity-validation.ps1, and the pwsh self-test files themselves.

  5. wallstop commented on Oct 11, 2026

    @wallstop
    OwnerAuthor

    DISCLOSURE: LLM-GENERATED TEXT

    Fifth slice + a local-iteration finding (2026-10-11)

    Slice (PR #177, harness robustness): the recycle contract fixture was the last fixture whose death degraded into wrong verdicts; it now proves spawn readiness with an exec marker, retries once, and fails by name (issue #175, which also red-lighted main at run 38163069900). Not a language port, but it removes the last suite flake that cost CI reruns.

    Finding: the devcontainer checkout mount dominates local iteration time. /workspaces here is a 9p drvfs mount of the Windows host drive (msize=64 KB); 9p metadata traffic dominates spawn-heavy harness work. Same-window A/B, identical tree and dirty surface, container-native ext4 clone vs the mount:

    Measurement 9p (/workspaces) ext4 (~/ clone)
    fast-check plan floor 4.4 s 0.28 s
    whole dirty-tree check:fast (same diff, both green) 25.6 s 12.1 s
    full 28-file self-test suite 86.0 s 39.7 s

    The suite number explains why hosted CI (native disk, 4 vCPU) measured 41 s while this 12-vCPU container measured 86 s. Recorded as agent guidance in PR #178 (clone under the Linux home for iteration loops; keep /workspaces for Unity host-bridge work). CI adoption of uv stays deferred as before; this finding is local-only by construction.

    Remaining surface for this issue (unchanged): the six node lints, the release .mjs pipeline and its five self-test files, build-docs.ps1, run-unity-validation.ps1, and the 27 remaining pwsh self-test files. The suite is contention-bound (per-file serial walls 1.9-6.3 s vs 13-77 s under 28-way concurrency on 9p), so porting the self-test files to Python is the next structural lever for both local and CI-suite walls.

  6. 113 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions