Skip to content

Add a Windows Counterpart to the Linux Bootstrap Loader - #682

Merged
ptr727 merged 3 commits into
developfrom
feature/windows-bootstrap-loader
Aug 13, 2026
Merged

Add a Windows Counterpart to the Linux Bootstrap Loader#682
ptr727 merged 3 commits into
developfrom
feature/windows-bootstrap-loader

Conversation

@ptr727

@ptr727 ptr727 commented Aug 13, 2026

Copy link
Copy Markdown
Owner

What

Adds host-setup/bootstrap.ps1, a Windows counterpart to host-setup/bootstrap.sh, so a Windows host with nothing can be stood up from a copy-paste console snippet the same way a Linux host already can.

Why

Windows previously carried no loader, by a documented decision (host-setup/README.md, docs/host-setup.md, host-setup/windows/README.md's "Why There Is No bootstrap.ps1"): "the Windows form of that problem has no one-liner anybody here has run, and an unverified loader is worse than none." This PR supersedes that decision rather than ignoring it — the reasoning is kept and repurposed as the reason bootstrap.ps1 does more defensive work than its Linux peer (TLS pinning, a tree-ownership marker, execution-policy handling, self-installing its own prerequisite) rather than as the reason none exists.

How

  • bootstrap.ps1 runs under Windows PowerShell 5.1, the one shell a fresh Windows host guarantees. It finds or installs PowerShell 7 via winget, then hands the rest of the run to itself under pwsh.
  • From there it mirrors bootstrap.sh: resolves the ref to a commit, fetches/extracts the tarball with an ownership marker guarding cleanup, and drives upgrade-host.ps1install-tools.ps1setup-github.ps1 in the same fixed order (packages first, GitHub last since it's the only step waiting on a human in a browser).
  • Adds a new -Wsl action (report-only — setup-wsl.ps1 has no Linux peer, and no flag here collects a distribution name to install one unattended).
  • Sits beside bootstrap.sh at host-setup/ rather than inside host-setup/windows/, the same one-concern-two-platforms shape agent-safety/install.sh + install.ps1 already uses.
  • Docs (host-setup/README.md, docs/host-setup.md, host-setup/windows/README.md) updated to describe the new loader and its Windows one-liner, replacing the "no bootstrap.ps1" sections.
  • scripts/test_bootstrap.py extended: the loader-invariant tests (fetched-tree-path, no-Python) now run against both loaders, plus a new presence/no-shebang test for bootstrap.ps1.

What this does not verify

Neither loader (the existing bootstrap.sh nor this new bootstrap.ps1) has ever been run against a genuinely fresh host — only dry runs and reads against an already-configured checkout. TODO.md now tracks that as its own work cluster ("Neither Host Bootstrap Has Run Against a Truly Fresh Host"), since closing it needs a human watching a real run on a real fresh VM image, not more code.

Verification performed

  • PSScriptAnalyzer (pinned 1.23.0, matching CI): clean.
  • scripts/test_bootstrap.py: passing.
  • scripts/prose_lint.py . --diff HEAD and scripts/repo_gate.py --check eol: clean (the local pre-commit gate).
  • Parsed and ran -Help under actual Windows PowerShell 5.1.
  • Full -DryRun runs against develop under pwsh for -Report, -Host -Yes, -Wsl, and the PS5.1→pwsh7 reinvocation handoff forced via powershell.exe — all completed cleanly, correctly drove the real tool scripts, and left no orphaned trees afterward.
  • Confirmed two-actions-given correctly refuses, matching the sibling scripts' convention.

Windows carried no equivalent of bootstrap.sh, by a documented decision:
"the Windows form of that problem has no one-liner anybody here has run,
and an unverified loader is worse than none." This supersedes that
decision rather than ignoring it. bootstrap.ps1 runs under Windows
PowerShell 5.1, the one shell a fresh Windows host guarantees, finds or
installs PowerShell 7 through winget, hands the rest of the run to
itself under pwsh, then drives upgrade-host.ps1, install-tools.ps1 and
setup-github.ps1 in the same fixed order bootstrap.sh drives their Linux
peers, plus a new -Wsl action reporting the WSL platform, which has no
Linux side to mirror.

It sits beside bootstrap.sh at host-setup/ rather than inside
host-setup/windows/, the same one-concern-two-platforms shape
agent-safety/ already uses for install.sh and install.ps1.

Neither loader has ever run against a genuinely fresh host, only dry
runs and reads against an already-configured checkout. TODO.md tracks
that as its own item, since closing it needs a human on a real VM image
rather than more code here.
Copilot AI lite review requested due to automatic review settings August 13, 2026 18:16

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

Adds a Windows bootstrap loader (host-setup/bootstrap.ps1) that mirrors the existing Linux loader (bootstrap.sh), enabling “copy/paste from a stock host” setup on native Windows. The PR also updates docs and tests to reflect/support having two platform bootstraps.

Changes:

  • Add host-setup/bootstrap.ps1 (PowerShell 5.1 entrypoint that installs/locates PowerShell 7, fetches/extracts a tarball, and drives host-setup/windows/* scripts).
  • Extend scripts/test_bootstrap.py to assert the loader invariants for both bootstraps and to validate bootstrap.ps1 presence/no-shebang.
  • Update host setup documentation and TODO tracking to describe the new Windows bootstrap path and record the still-unverified “fresh host” runs.

Reviewed changes

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

Show a summary per file
File Description
TODO.md Adds a tracked work cluster for verifying both bootstraps on truly fresh images; adds link refs for the two loaders.
scripts/test_bootstrap.py Refactors loader-invariant assertions to run against both bootstrap.sh and bootstrap.ps1; adds a bootstrap.ps1 presence/no-shebang test.
host-setup/windows/README.md Replaces the “no bootstrap.ps1” rationale with usage/behavior notes for the new Windows loader and updates examples/status list.
host-setup/README.md Documents the new Windows one-liner and explains the PS 5.1 → PS 7 handoff behavior.
host-setup/bootstrap.ps1 New Windows bootstrap loader implementing ref resolution, tarball fetch/extract with ownership marker, and fixed-order tool execution (plus report-only WSL action).
docs/host-setup.md Updates the host-setup contract narrative to include the Windows loader and its “no Python” implications.

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

Comment thread host-setup/bootstrap.ps1 Outdated
$DIR = '' at top level was the same variable as the -Dir parameter
$Dir, since PowerShell variable names are case-insensitive, and it
reset that parameter to empty before Resolve-Directory ever read it,
so -Dir has been ignored since the file was added. Found by GitHub
Copilot's review of the previous commit. Verified fixed: bootstrap.ps1
-Dir <path> -DryRun -Keep now extracts under the named path rather
than the default.

Also converts the file to CRLF, matching the other host-setup/windows
scripts and the .editorconfig [*] default (end_of_line = crlf), which
editorconfig-checker enforces in CI. An earlier grep-based check of
the sibling files' line endings gave a false negative; a byte-level
read shows they were CRLF all along.
Copilot AI review requested due to automatic review settings August 13, 2026 18:25

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 6 out of 6 changed files in this pull request and generated 1 comment.

Suppressed comments (2)

host-setup/bootstrap.ps1:137

  • Resolve-Pwsh unconditionally calls Join-Path ${env:ProgramFiles(x86)} .... On systems where ProgramFiles(x86) is unset (e.g., 32-bit Windows / unusual environments), Join-Path will throw (and $ErrorActionPreference = 'Stop' makes it terminating), preventing the loader from running.
    foreach ($candidate in @(
            (Join-Path $env:ProgramFiles 'PowerShell\7\pwsh.exe')
            (Join-Path ${env:ProgramFiles(x86)} 'PowerShell\7\pwsh.exe')
        )) {
        if (Test-Path $candidate) { return $candidate }

host-setup/bootstrap.ps1:150

  • Install-Pwsh doesn't check winget's exit code. If winget install fails, the script still continues and then errors with a misleading message about winget reported installing PowerShell 7.
    step 'Installing PowerShell 7'
    & winget install --id Microsoft.PowerShell --exact --source winget --accept-source-agreements --accept-package-agreements --silent --disable-interactivity | Out-Host
    $found = Resolve-Pwsh
    if (-not $found) {
        die 'winget reported installing PowerShell 7, but pwsh could still not be found. Close this console and paste the setup lines again, or install it from https://aka.ms/PSWindows.'

Comment thread host-setup/bootstrap.ps1
Resolve-Pwsh unconditionally Join-Path'd ${env:ProgramFiles(x86)},
which is unset on a host with no WOW64 layer, and Join-Path on a null
path is a terminating error under Set-StrictMode rather than a
candidate that fails to match. Skip that candidate when the variable
carries nothing.

Install-Pwsh now names winget's own exit code in the die message when
pwsh still can't be found afterward, rather than only "reported
installing", so a genuine winget failure reads as one.

Both found by GitHub Copilot's low-confidence findings on the previous
round; the inline finding on that round (-UseBasicParsing failing
under pwsh 7) was checked against a live pwsh 7.6.4 process and
disproven, replied to in the PR thread.
Copilot AI review requested due to automatic review settings August 13, 2026 18:33
@ptr727

ptr727 commented Aug 13, 2026

Copy link
Copy Markdown
Owner Author

Answering the 2 suppressed (low-confidence) findings from the review round on 5a700a7 ("Suppressed comments (2)"):

host-setup/bootstrap.ps1:137Resolve-Pwsh unconditionally Join-Paths ${env:ProgramFiles(x86)}, which is unset on a host with no WOW64 layer, and Join-Path on a null path is a terminating error under Set-StrictMode.
Fixed in 157689f: that candidate is now only added to the search list when the variable actually carries a value.

host-setup/bootstrap.ps1:150Install-Pwsh didn't check winget's exit code, so a genuine winget install failure surfaced only as the generic "reported installing... but pwsh could still not be found" message.
Fixed in 157689f: winget's exit code is now named in the die message when pwsh still can't be found afterward. Kept the existing "check whether pwsh was actually found" as the authoritative signal rather than trusting the exit code alone — that's still the more reliable check (a 0 exit with pwsh still missing is a real, if unlikely, case worth surfacing too) — but the exit code is now included for diagnosis either way.

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 6 out of 6 changed files in this pull request and generated no new comments.

@ptr727
ptr727 merged commit 2ce4006 into develop Aug 13, 2026
7 checks passed
@ptr727
ptr727 deleted the feature/windows-bootstrap-loader branch August 13, 2026 18:47
ptr727 added a commit that referenced this pull request Aug 15, 2026
…Python CI Gates (#718)

Thirty-one squashes, `56f4d7d..d54862a`. 115 files, +20436/-5298.

**Merge with a merge commit, never a squash, and never with
`--delete-branch`.** This pull request's head is `develop` itself.

## What lands

**Fleet Skills.** The `.agents/skills/` source tree, the generated
`.claude-plugin/` distribution, `scripts/build_dist.py` with its
`--check` gate, and `scripts/skills_install.py` with its host stamp
(#676). Packaged as skills on top of the scaffold: PR review conduct and
Copilot instructions upkeep (#677), comment and doc style (#678),
resync-a-repo and fleet-conformance-check (#679), the per-language
codestyles (#680), git commit conventions and operational vs release
workflow (#681), stand up a repo (#683), and repo-worktree (#717).
Coverage gaps closed in three passes (#690, #691, #692) plus the P4
sentence-length opt-in (#697).

**Host setup.** The Windows host-setup tooling and its PowerShell gate
(#674), the Windows bootstrap loader (#682), Docker install and upgrade
on Linux and Windows with a version floor (#701, #705), a `uv` floor in
`spec/host-tools.json` (#698), self-healing of a shadowing `uv`, `jq`,
or `git-restore-mtime` copy (#689), node's real winget package id
(#696), and a README for the Linux host-setup nuances (#710).

**Python and CI.** Python tooling in CI with the script tests moved to
`scripts/tests` (#704), `ruff format` adopted and gated (#709), and the
PSScriptAnalyzer claim conditioned on repos that carry `.ps1` files
(#686).

**Conduct rules.** Triage-order and scope guardrails in
pr-review-conduct (#684), `pr_review.py wait` requesting a review rather
than only polling for one (#685), a tech-agnostic signed-commit
verification (#708), execution rather than analogy to verify
platform-specific code (#715), and a unique worktree for every task
(#717).

**Docs.** The fleet map and gap register with peer messaging declared
(#687), mermaid flow diagrams in the kept-authority docs (#702), and the
map pointed at the shipped diagrams and current tooling (#703).

## Issues this promotion closes

Each landed on `develop` on its own pull request. The keyword fires only
on a merge into `main`, so it sits here rather than on the feature pull
requests.

Closes #700
Closes #707
Closes #711
Closes #712
Closes #714
Closes #688

#699 stays open on purpose: #717 shipped the layout convention and the
skill, and the physical migration of existing checkouts is still tracked
there.

## Review record

Every squash closed its own Copilot loop on its own pull request before
merging to `develop`. This promotion carries no new content of its own,
so its review is the merged tree as a whole.

## Consequence worth stating

The `GOVERNANCE.md` and `AGENTS.md` sections these squashes changed
become the canonical the moment this reaches `main`, and every carrying
repository reads as drifted from that point until it resyncs. That is
the ordinary consequence of a canonical moving rather than a defect. The
Skills installer added here is also how a machine picks the new skills
up, so a session that keeps restating a rule already packaged as a skill
is the signal to run it.
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