:: run_setup.bat -- Python vs Windows
:: Project Home: https://github.com/mixmansoundude/Python_vs_Windows
:: Description: Automated, zero-config Python environment management for Windows. Standalone and portable.
:: [VERSION_METADATA]
:: Last Verified Date: 2026-06-19
:: Verified Windows: Windows 10/11 (CI runner: Windows Server 2025)
:: Verified PowerShell: 5.1+
:: Verified Python: 3.14.6 (CI Latest)
:: NOTE: Update 'Last Verified Date' only when 'Verified Python' version changes
::   or this entry is more than 6 months stale. Do NOT update the date on every commit.
:: RECOVERY: If a future Python version breaks auto-detection, install a Python version matching the
::   'Last Verified' metadata above, then run 'set PVW_PYTHON_EXE=C:\path\to\python.exe' in your
::   terminal before running this script to bypass the broken detection.
:: ============================================================
:: FIRST-TIME WINDOWS USERS: SmartScreen / Security Warning
:: ============================================================
:: When you double-click this file, Windows may show:
::   "Windows protected your PC"
:: This is normal for any .bat file downloaded from the internet.
::
:: TO RUN:  Click "More info"  then  "Run anyway"
::
:: IF "Run anyway" is not shown:
::   1. Close this dialog
::   2. Right-click run_setup.bat in File Explorer
::   3. Click "Properties"
::   4. At the bottom, check the "Unblock" checkbox
::   5. Click "OK"
::   6. Double-click run_setup.bat again
:: ============================================================
:: FOR CONTRIBUTORS/EDITORS OF THIS FILE (not needed to just run it):
:: Do NOT edit this file using the GitHub web editor, "Edit in place", or
:: any Mac/Linux text tool -- these silently strip the CR half of this
:: file's required Windows (CRLF) line endings, corrupting it for every
:: Windows user who later downloads it. This has broken things before
:: (see docs/agent-closed-backlog.md's Item 44). Edit only from a real
:: Windows/CRLF checkout, and run
:: `python tools/check_crlf.py --fix run_setup.bat` before committing if
:: unsure -- also enforced by a gating CI check on every PR.
:: ============================================================
@echo off
setlocal DisableDelayedExpansion
rem ============================================================
rem LINE-ENDING SELF-CHECK -- keep this first, before any goto/call in
rem this file. Defense-in-depth, not a fix for a known-broken distribution
rem channel: this file's line endings are Windows ^(CRLF^) by construction
rem and enforced in CI ^(.gitattributes' "-text" for *.bat/*.cmd plus
rem tools/check_crlf.py -- a raw/blob download via GitHub's "Raw" button or
rem a raw.githubusercontent.com link is expected to serve genuine CRLF^),
rem but an old cached download from before that fix, or a copy re-saved by
rem an editor that does not preserve CRLF, can still corrupt a user's copy.
rem cmd.exe's goto/call label lookup can silently misbehave on an LF-only
rem copy of a file this size -- wrong-label errors, skipped blocks,
rem corrupted commands -- producing a confusing partial run instead of a
rem clear failure. This check must therefore be self-reliant ^(no goto/call
rem anywhere in this file, since that is exactly what an LF-only copy
rem breaks^) and must run before anything else.
rem ============================================================
rem HP_PREFLIGHT_STATUS is script-rooted via %~dp0 (not CWD-relative, and not the
rem later %STATUS_FILE%/:write_status machinery -- neither exists yet at this point
rem in the file, and :write_status itself is call-based, unsafe to invoke before the
rem line-ending check above has passed). Written directly, only on a preflight
rem failure below, so a stale "ok" status from an earlier successful run in this
rem same folder can never be misread as this run's result if this run's copy of
rem the file cannot even get past its own preflight.
set "HP_PREFLIGHT_STATUS=%~dp0~bootstrap.status.json"
where powershell >nul 2>&1
if defined HP_TEST_FORCE_NO_POWERSHELL (
  rem derived requirement: force a nonzero errorlevel here without touching PATH,
  rem so CI can deterministically exercise the branch below on a shared runner
  rem with no risk of actually breaking PowerShell resolution for it.
  cmd /c "exit /b 1"
)
if errorlevel 1 (
  echo ***
  echo *** [ERROR] PowerShell was not found on this machine.
  echo *** This script requires PowerShell to run at all; please
  echo *** repair or reinstall it, then run this script again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
if defined HP_TEST_FORCE_LF_ONLY (
  rem derived requirement: point the check at a synthetic LF-only file instead
  rem of this running copy, so CI can exercise the invalid-line-endings branch
  rem below without corrupting the file that is actually executing right now.
  set "HP_SELF_PATH=%~dp0~test_lf_only.bat"
) else if defined HP_TEST_FORCE_PS_CHECK_FAIL (
  rem derived requirement: point at a path that does not exist, so the real
  rem PowerShell command below genuinely throws and hits its own catch branch
  rem -- exercises the real failure path instead of faking an exit code
  rem externally.
  set "HP_SELF_PATH=%~dp0~test_missing_for_ps_check.bat"
) else (
  set "HP_SELF_PATH=%~f0"
)
powershell -NoProfile -Command "try{$c=[System.IO.File]::ReadAllText($env:HP_SELF_PATH);$crlf=-join @([char]13,[char]10);$lf=[string][char]10;$cr=[string][char]13;$norm=$c.Replace($crlf,'');if($c.Contains($crlf) -and -not $norm.Contains($lf) -and -not $norm.Contains($cr)){exit 0}else{exit 1}}catch{exit 2}" >nul 2>&1
if errorlevel 2 (
  echo ***
  echo *** [ERROR] PowerShell could not check the line endings of this file.
  echo *** This may mean PowerShell is restricted on this machine -- for
  echo *** example, by a Constrained Language Mode policy -- please repair
  echo *** or unblock PowerShell, then run this script again.
  echo ***
  echo {"state":"error","exitCode":2,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 2
)
if errorlevel 1 (
  echo ***
  echo *** [ERROR] This copy of run_setup.bat has invalid line endings.
  echo *** Every line ending in this file must be Windows-style ^(CRLF^); at
  echo *** least one is not, and the file will fail with confusing, partial,
  echo *** hard-to-diagnose errors if run as-is.
  echo ***
  echo ***   Easiest fix: delete this copy and download run_setup.bat again --
  echo ***   the GitHub "Raw" link and the download page are both fine now.
  echo ***   This is usually an old cached copy, or one re-saved by an editor
  echo ***   that does not preserve Windows line endings.
  echo ***
  echo ***   Or open this file in an editor that shows line endings
  echo ***   ^(e.g. Notepad++, VS Code^) and convert it to Windows ^(CRLF^)
  echo ***   line endings, then save and run this script again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
set "DEP_SOURCE=unknown"
rem [REQ-026] Argv passthrough escape hatch (docs/plan-cli-interactive-verification.md P1):
rem capture trailing arguments (%2-%9) here, before anything else touches %1-%9, and forward
rem them verbatim to the target program at every real launch site (EXE smoke, EXE fast-path
rem reuse, interpreter run, checkpoint's elective second run). Deliberately does NOT use `shift`
rem -- %~1 (the entry file) is read directly via "%~1" at multiple later points in this file
rem (the UNC check below, both :determine_entry call sites), and shifting here would silently
rem change what those later "%~1" reads see. Practical limit of 8 extra args (%2-%9): CMD has
rem no way to read beyond %9 without `shift`, and `shift` is off-limits for the reason above.
rem Each present token is individually re-quoted (not just %~2 raw) so a token containing spaces
rem survives as ONE argv element to the target program, matching ordinary Windows command-line
rem quoting; a token containing a literal " is not supported (documented limitation, not silently
rem mishandled -- see README [REQ-026]).
set "HP_APP_ARGS="
if not "%~2"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~2""
if not "%~3"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~3""
if not "%~4"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~4""
if not "%~5"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~5""
if not "%~6"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~6""
if not "%~7"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~7""
if not "%~8"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~8""
if not "%~9"=="" set "HP_APP_ARGS=%HP_APP_ARGS% "%~9""
rem Boot strap renamed to run_setup.bat
set "HP_SCRIPT_LAUNCH_DIR=%~dp0"
if "%HP_SCRIPT_LAUNCH_DIR:~0,2%"=="\\" (
  rem derived requirement: parentheses must be escaped inside IF blocks in CMD or parsing breaks.
  echo *** WARNING: UNC/network paths detected ^(\\server\share^).
  echo *** This script may fail in this environment.
  echo *** Recommended: Map the network path to a drive letter and re-run.
  echo ***
)
if not exist "%~dp0" (
  echo [ERROR] Workspace path invalid: %~dp0
  exit /b 1
)
cd /d "%~dp0"
if errorlevel 1 (
  echo [ERROR] Workspace path invalid: %~dp0
  exit /b 1
)
rem derived requirement: writable-CWD preflight (CLAUDE.md Item 48) -- this is the first
rem point in the file safe to attempt a real write (CWD is now confirmed to be the app
rem folder itself, via the cd /d above), and it runs before every other write in this file
rem (the CI marker, ~setup.log, and :merge_git_config's own .gitignore/.gitattributes
rem writes) so an unwritable folder fails with one clear, named message instead of a
rem confusing cascade of silently-swallowed write failures later.
rem derived requirement: clear any pre-existing ~wtest.tmp before probing -- a crash between
rem a past successful probe and its own cleanup (or, unlikely given the tilde convention, a
rem coincidentally-named leftover) must not let a stale entry at this exact path masquerade as
rem this run's own successful write. del only removes a FILE at this path; if a directory of
rem the same name exists instead (however unlikely), del is a silent no-op against it, so rd
rem /s /q runs too whenever the entry is still present afterward -- between the two, either
rem shape of stale leftover is cleared before the real probe below ever runs.
del /f /q "~wtest.tmp" >nul 2>&1
if exist "~wtest.tmp" rd /s /q "~wtest.tmp" >nul 2>&1
if defined HP_TEST_FORCE_CWD_NOT_WRITABLE (
  rem derived requirement: force the not-writable branch deterministically for CI, without
  rem actually revoking filesystem permissions on a shared runner -- skip the real write
  rem attempt entirely so ~wtest.tmp is never created, the same signal a genuine write
  rem failure would produce. Intentionally empty otherwise: doing nothing IS the forcing
  rem mechanism here.
) else (
  type nul > "~wtest.tmp" 2>nul
)
if not exist "~wtest.tmp" (
  echo ***
  echo *** [ERROR] This folder does not appear to be writable: "%CD%"
  echo *** This script needs to create files here -- logs, a dependency cache, and
  echo *** eventually a standalone program. Move this script and your .py files to
  echo *** a folder you have write access to, then run it again.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
del /f /q "~wtest.tmp" >nul 2>&1
rem derived requirement: PowerShell capability preflight (CLAUDE.md Item 47) -- placed after
rem the CWD-writable check above so a real folder-permission failure is diagnosed by that
rem check first, not misattributed to this one. Bare PowerShell presence, checked earlier in
rem the line-ending self-check, only proves powershell.exe exists on PATH, not that it can do
rem what this bootstrapper actually needs: :emit_from_base64, used to write every embedded
rem ~*.py/~*.ps1 helper, needs Convert.FromBase64String plus IO.File.WriteAllBytes, and the
rem failfast-probe/exe-smokerun helpers need System.Diagnostics.ProcessStartInfo -- all of
rem which a locked-down corporate image, such as AppLocker, WDAC, or Constrained Language
rem Mode, can block even with PowerShell itself present. Probing all three together here turns
rem five-plus later opaque "Could not write ~x" failures into one clear, named diagnostic.
rem derived requirement: %RANDOM% suffix (two draws) makes the probe path unique per process --
rem this preflight runs before :acquire_lock below, so two genuinely concurrent instances in the
rem same folder must not be able to race on (delete/overwrite) each other's probe file and
rem misreport a capability failure that isn't real. Not a full mutex, just enough entropy that a
rem same-folder collision within one preflight's brief lifetime is negligible.
set "HP_PS_PROBE_NAME=~ps_capability_probe.%RANDOM%%RANDOM%.tmp"
set "HP_PS_PROBE_FILE=%~dp0%HP_PS_PROBE_NAME%"
if defined HP_TEST_FORCE_PS_CAPABILITY_FAIL (
  rem derived requirement: point the probe at a nonexistent directory so the real
  rem WriteAllBytes call genuinely throws and hits its own catch branch -- exercises the
  rem real failure path instead of faking an exit code externally, matching
  rem HP_TEST_FORCE_PS_CHECK_FAIL's established technique above.
  set "HP_PS_PROBE_FILE=%~dp0~nonexistent_dir_xyz\%HP_PS_PROBE_NAME%"
)
del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
rem derived requirement: [IO.File]::Exists/Delete, not Test-Path/Remove-Item -- the latter two
rem PowerShell cmdlets treat "[" and "]" in a path as wildcard syntax by default, so a folder
rem name containing literal brackets could make Test-Path report a genuinely-written file as
rem missing, misreporting a capability failure that isn't real. The .NET IO.File methods used
rem here never glob-expand, so the app folder's own path can contain any legal Windows filename
rem character without affecting this probe's outcome.
powershell -NoProfile -Command "try{$b=[Convert]::FromBase64String('cHZ3');[IO.File]::WriteAllBytes($env:HP_PS_PROBE_FILE,$b);if(-not [IO.File]::Exists($env:HP_PS_PROBE_FILE)){exit 1};[IO.File]::Delete($env:HP_PS_PROBE_FILE);$psi=New-Object System.Diagnostics.ProcessStartInfo;exit 0}catch{exit 1}" >nul 2>&1
if errorlevel 1 (
  echo ***
  echo *** [ERROR] PowerShell on this machine cannot perform an operation this
  echo *** script needs -- decoding embedded data, writing files, or preparing
  echo *** to launch a process. This is usually caused by a restrictive policy
  echo *** such as Constrained Language Mode, AppLocker, or WDAC on a managed
  echo *** or corporate machine. Ask an administrator to allow PowerShell Full
  echo *** Language Mode for this script, or run it on an unrestricted machine.
  echo ***
  echo {"state":"error","exitCode":1,"pyFiles":0}> "%HP_PREFLIGHT_STATUS%"
  del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
  if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
  if not defined HP_CI_LANE ( pause )
  exit /b 1
)
del /f /q "%HP_PS_PROBE_FILE%" >nul 2>&1
if exist "%HP_PS_PROBE_FILE%" rd /s /q "%HP_PS_PROBE_FILE%" >nul 2>&1
set "HP_SCRIPT_ROOT=%~dp0"
for %%R in ("%HP_SCRIPT_ROOT%") do set "HP_SCRIPT_ROOT=%%~fR"
if not "%HP_SCRIPT_ROOT:~-1%"=="\" set "HP_SCRIPT_ROOT=%HP_SCRIPT_ROOT%\"
set "HP_CI_MARKER=.ci_bootstrap_marker"
type nul > "%HP_CI_MARKER%" 2>nul
set "LOG=~setup.log"
set "LOGPREV=~setup.prev.log"
set "STATUS_FILE=~bootstrap.status.json"
if not exist "%LOG%" (type nul > "%LOG%")
rem derived requirement (real CI failure, PR #455): :merge_git_config now needs an HP_*
rem payload (HP_MIGRATE_GITATTRIBUTES, Item 60) at its own :emit_from_base64 call site, so
rem :define_helper_payloads must run BEFORE this call, not at its old position ~300 lines
rem later -- see the paired removal below, near the old ":rotate_log"/"CI fast path" site.
call :define_helper_payloads
call :merge_git_config
if "%HP_TEST_FORCE_CONNECTIVITY_CHECK%"=="1" call :check_net_after_dl_fail
if "%HP_TEST_FORCE_CONSENT_CHECK%"=="1" (
  call :system_python_consent_gate
  if errorlevel 1 (
    call :log "[INFO] REQ-014: Consent gate test: user declined."
    exit /b 1
  )
  call :log "[INFO] REQ-014: Consent gate test: user accepted."
)
rem --- PVW_ super-user overrides (inherit from calling terminal; logged before detection runs) ---
rem derived requirement: PVW_ variables let a super-user pre-set values to bypass auto-detection.
rem Single-line if form avoids parse-time expansion issues in block-form if-statements when a
rem variable value contains parentheses, such as a path under "C:\Program Files (x86)\...".
if defined PVW_PYTHON_EXE call :log "[DEBUG] Using super-user override for PVW_PYTHON_EXE: %PVW_PYTHON_EXE%"
if defined PVW_UV_EXE call :log "[DEBUG] Using super-user override for PVW_UV_EXE: %PVW_UV_EXE%"
if defined PVW_CONDA_EXE call :log "[DEBUG] Using super-user override for PVW_CONDA_EXE: %PVW_CONDA_EXE%"
if defined PVW_TARGET_PY call :log "[DEBUG] Using super-user override for PVW_TARGET_PY: %PVW_TARGET_PY%"
if defined PVW_WORKSPACE call :log "[DEBUG] Using super-user override for PVW_WORKSPACE: %PVW_WORKSPACE%"
rem --- Path-length guard: warn if script root path approaches the 260-char cmd.exe limit ---
for /f "usebackq delims=" %%L in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$env:HP_SCRIPT_ROOT.Length" 2^>nul`) do set "HP_PATH_LEN=%%L"
if defined HP_PATH_LEN if %HP_PATH_LEN% GEQ 200 (
  echo *** WARNING: Script path is %HP_PATH_LEN% chars. Paths near 260 chars may cause cmd.exe failures.
  call :log "[WARN] Script path is %HP_PATH_LEN% chars; paths near 260 chars may cause failures."
)
set "HP_PATH_LEN="
rem --- Synced-folder guard: warn when running from a OneDrive or similar sync folder ---
if /I not "%HP_SCRIPT_ROOT:OneDrive=%"=="%HP_SCRIPT_ROOT%" (
  echo *** WARNING: Script appears to be in a OneDrive folder. File locking may cause failures.
  call :log "[WARN] OneDrive path detected; file locking may cause failures."
)
rem --- System-directory guard: check-and-abort (not warn-only) if the script root is under a
rem Windows system folder. Unlike the OneDrive/path-length/disk-space guards above (marginal
rem conditions that MIGHT cause a failure), a user occasionally drops this script into
rem %WINDIR%/%PROGRAMFILES% thinking it "installs" there -- without elevation this will
rem essentially always fail on write-permission errors several steps in (creating ~setup.log,
rem ~uv_bin, .uv_env, etc. right next to itself), so a clear early abort is kinder than a
rem cryptic failure deep in the bootstrap. findstr substring match mirrors the UNC-path check
rem at the top of this file; HP_SCRIPT_ROOT always has a trailing backslash (see above), so a
rem trailing backslash on the search pattern prevents a same-prefix false match (e.g.
rem "C:\WindowsFooBar\" does not contain the substring "C:\Windows\").
set "HP_SYSDIR_HIT="
set "HP_PF86=%ProgramFiles(x86)%"
rem derived requirement: the search pattern ends in "\\" (two backslashes), not "\" -- a
rem SINGLE backslash immediately before the closing quote is a classic Windows argv-parsing
rem trap: findstr.exe (like most native console apps) follows the standard C-runtime rule
rem that an ODD number of backslashes right before a closing quote escapes the quote instead
rem of closing the string, silently corrupting the whole /C: argument (and swallowing the
rem trailing ">nul" into the search pattern) so the match can never succeed. An EVEN count
rem (here, two) collapses to a single literal backslash and the quote closes normally.
rem derived requirement: a CodeRabbit review finding (PR #417) -- HP_SCRIPT_ROOT is echoed
rem UNQUOTED into a pipe here; if it contains '&', cmd.exe's own parser (which does not
rem distinguish "this & came from a variable" from "this & was typed") treats it as a command
rem separator, splitting this single line into two commands and feeding findstr only a
rem truncated prefix -- silently defeating the guard for a script dropped under a path like
rem "C:\Users\Sales & Marketing\run_setup.bat". Quoting protects it: cmd.exe tracks quote state
rem left-to-right as it scans (including through %VAR% expansion), so a '&' landing inside the
rem quoted region is never treated as an operator. The literal quote characters `echo` leaves in
rem its own output (echo does not strip them, unlike most commands) don't affect the match --
rem findstr's /C: pattern is a substring search, so it still finds "%WINDIR%\\" etc. regardless
rem of the extra leading/trailing quote characters surrounding it.
if defined WINDIR (
  echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%WINDIR%\\" >nul
  if not errorlevel 1 set "HP_SYSDIR_HIT=1"
)
if defined ProgramFiles (
  echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%ProgramFiles%\\" >nul
  if not errorlevel 1 set "HP_SYSDIR_HIT=1"
)
if defined HP_PF86 (
  echo "%HP_SCRIPT_ROOT%"| findstr /I /C:"%HP_PF86%\\" >nul
  if not errorlevel 1 set "HP_SYSDIR_HIT=1"
)
set "HP_PF86="
if defined HP_SYSDIR_HIT (
  echo *** ERROR: This script is located inside a Windows system folder.
  echo *** Placing it here does not "install" it. Windows restricts writes to this location
  echo *** without administrator rights, and this bootstrapper needs to create files right
  echo *** next to itself to work.
  echo *** Please move this script ^(and your .py files^) to a normal folder -- your Desktop
  echo *** or Documents folder both work well -- then run it again from there.
  call :log "[ERROR] System-directory guard: script root is under a Windows system folder; aborting."
  exit /b 1
)
set "HP_SYSDIR_HIT="
rem --- Free-disk-space guard: warn (never abort) if the script's drive looks low on space ---
rem derived requirement: Miniconda download/install plus conda env creation can together need
rem several hundred MB to a few GB; a low-disk-space beginner machine would otherwise see whatever
rem low-level error curl/conda/pip happens to surface for "no space left on device" deep inside the
rem bootstrap, instead of a clear, early, plain-language message. Threshold is deliberately generous
rem (2 GB) and this only warns -- per REQ-001 (env-var flags are scaffolding, never a Prime-Directive
rem gate), a low reading must never hard-block the run: the user may still have just enough, or may
rem free space and retry after seeing the warning.
set "HP_FREE_GB="
if defined HP_TEST_FORCE_LOW_DISK (
  set "HP_FREE_GB=0"
) else (
  for /f "usebackq delims=" %%D in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $letter = ([System.IO.Path]::GetPathRoot($env:HP_SCRIPT_ROOT)).Substring(0,1); [math]::Floor((Get-PSDrive -Name $letter).Free / 1GB) } catch { '' }" 2^>nul`) do set "HP_FREE_GB=%%D"
)
if defined HP_FREE_GB if %HP_FREE_GB% LSS 2 (
  echo *** WARNING: Only ~%HP_FREE_GB% GB free disk space detected on this drive.
  echo *** Downloading Python/Miniconda and building your app can need several GB.
  echo *** If setup fails partway through, freeing up disk space is a likely fix.
  call :log "[WARN] REQ-025: low disk space detected (~%HP_FREE_GB% GB free); continuing (warn-only)."
)
set "HP_FREE_GB="
if exist "%STATUS_FILE%" del "%STATUS_FILE%"
rem REQ-024: concurrent-instance protection -- must run before any real bootstrap work
rem (env creation, downloads, EXE fast path) so two double-clicks in this folder cannot race.
call :acquire_lock
if errorlevel 1 exit /b 1
set "HP_BOOTSTRAP_STATE=ok"
rem REQ-010: nullify host-system Python path variables to prevent library interference
set "PYTHONPATH="
set "PYTHONHOME="
set "HP_ENV_MODE=conda"
set "HP_ENV_READY="
set "HP_SKIP_PIPREQS=%HP_SKIP_PIPREQS%"
set "HP_PY="
set "HP_UV_PROVIDING_PYTHON="
set "HP_FIND_ENTRY_SYNTAX_OK="
set "HP_HELPER_SYNTAX_EMITTED="
set "HP_HELPER_CMD_LOGGED="
set "HP_FIND_ENTRY_NAME=~find_entry.py"
set "HP_FIND_ENTRY_ABS="
set "HP_PIPREQS_VERSION=%HP_PIPREQS_VERSION%"
rem derived requirement: pin pipreqs to 0.4.13, NOT 0.5.0. pipreqs 0.5.0 added Jupyter
rem notebook scanning, which hard-pins ipython==8.12.3 (the last ipython supporting Python
rem 3.8). ipython 8.12.3 does not support Python 3.13+, so 0.5.0's metadata declares
rem Requires-Python >=3.8.1,<3.13. Because the bootstrapper always targets the latest
rem conda-forge Python (3.14+), 0.5.0 refuses to install there and pipreqs is lost entirely.
rem 0.4.13 has Requires-Python >=3.7 (no upper cap), deps only docopt+yarg, supports the same
rem --mode compat / --force / --savepath flags, uses only stable stdlib (ast-based scan), and
rem runs on Python 3.14. Do NOT "upgrade" back to 0.5.0 -- it reintroduces the <3.13 cap.
rem The only feature lost is .ipynb scanning, which was already non-functional on latest Python.
if not defined HP_PIPREQS_VERSION set "HP_PIPREQS_VERSION=0.4.13"
set "HP_MINICONDA_MIN_BYTES=%HP_MINICONDA_MIN_BYTES%"
if not defined HP_MINICONDA_MIN_BYTES set "HP_MINICONDA_MIN_BYTES=5000000"
set "HP_CONDA_DL_INJECTED="
if defined HP_MINICONDA_URL set "HP_CONDA_DL_INJECTED=1"
if not defined HP_MINICONDA_URL set "HP_MINICONDA_URL=https://repo.anaconda.com/miniconda/Miniconda3-latest-Windows-x86_64.exe"
set "HP_MINICONDA_FALLBACK_URL=https://repo.continuum.io/miniconda/Miniconda3-latest-Windows-x86_64.exe"
set "HP_UV_DL_INJECTED="
if defined HP_UV_URL set "HP_UV_DL_INJECTED=1"
if not defined HP_UV_URL set "HP_UV_URL=https://github.com/astral-sh/uv/releases/latest/download/uv-x86_64-pc-windows-msvc.zip"
rem HP_UV_FALLBACK_URL: pinned release used when primary GitHub releases/latest CDN fails
set "HP_UV_FALLBACK_URL=https://github.com/astral-sh/uv/releases/download/0.7.3/uv-x86_64-pc-windows-msvc.zip"
set "HP_UV_MIN_BYTES=%HP_UV_MIN_BYTES%"
if not defined HP_UV_MIN_BYTES set "HP_UV_MIN_BYTES=1000000"
rem REQ-023b: get-pip.py -- used only by the venv fallback tier's --without-pip retry (see
rem :download_get_pip / :try_venv_fallback). HP_GETPIP_FALLBACK_URL is the get-pip project's own
rem GitHub source (the file bootstrap.pypa.io serves is generated from this repo), mirroring the
rem primary-CDN + GitHub-source fallback pattern already used for Miniconda/uv above.
if not defined HP_GETPIP_URL set "HP_GETPIP_URL=https://bootstrap.pypa.io/get-pip.py"
set "HP_GETPIP_FALLBACK_URL=https://raw.githubusercontent.com/pypa/get-pip/main/public/get-pip.py"
rem REQ-009 Tier 5 (by naming/history; executed 3rd as of the provider-chain reorder): the
rem embeddable-Python fallback, reached right after conda fails (before venv/system) so a
rem pinned runtime.txt/pyproject.toml version is still honored via a fresh checksummed download
rem instead of silently falling back to whatever ambient Python happens to be on the machine.
rem Only a single "latest" version is ever downloaded here -- HP_EMBED_LATEST_SHA256 MUST match
rem the "3.14" entry embedded in the Python-side
rem ~embed_pyver_check.py payload below (a unit test asserts this so a refresh that updates one
rem but not the other is caught at CI time, not discovered live). See docs/agent-interconnect.md
rem "Standalone Python-download tier" for the two-stage PowerShell/Python design and why a
rem per-request version table deliberately does NOT live here in batch.
if not defined HP_EMBED_LATEST_PATCH set "HP_EMBED_LATEST_PATCH=3.14.6"
set "HP_EMBED_LATEST_SHA256=df901e84a896ff1ee720ad03377e0c8d8c2244fda79808aeeaff6316df1cb75c"
rem HP_TEST_OFFLINE=1: simulates ping failure for REQ-013 branch coverage (CI test flag)
set "HP_TEST_OFFLINE=%HP_TEST_OFFLINE%"
rem HP_OFFLINE_MODE is set by :check_net_after_dl_fail when user declines or no internet
set "HP_OFFLINE_MODE=%HP_OFFLINE_MODE%"
rem HP_TEST_FORCE_CONNECTIVITY_CHECK=1: triggers connectivity gate at startup for CI coverage
set "HP_TEST_FORCE_CONNECTIVITY_CHECK=%HP_TEST_FORCE_CONNECTIVITY_CHECK%"
rem HP_TEST_FORCE_VENV_FAIL=1: simulates venv creation failure for REQ-009/REQ-014 branch coverage
set "HP_TEST_FORCE_VENV_FAIL=%HP_TEST_FORCE_VENV_FAIL%"
rem HP_TEST_FORCE_VENV_CANARY_FAIL=1: simulates the post-creation canary probe (REQ-023) failing
rem after a real, successful venv creation (distinct from HP_TEST_FORCE_VENV_FAIL, which skips
rem creation entirely)
set "HP_TEST_FORCE_VENV_CANARY_FAIL=%HP_TEST_FORCE_VENV_CANARY_FAIL%"
rem HP_TEST_FORCE_VENV_CREATE_FAIL=1: simulates the FIRST plain "python -m venv" attempt failing
rem outright (e.g. a stripped-down host Python missing ensurepip) so the REQ-023b --without-pip
rem retry path is exercised for real; distinct from HP_TEST_FORCE_VENV_FAIL, which skips venv
rem creation entirely and never reaches either attempt.
set "HP_TEST_FORCE_VENV_CREATE_FAIL=%HP_TEST_FORCE_VENV_CREATE_FAIL%"
rem HP_TEST_FORCE_EMBED_FAIL=1: simulates the REQ-009 Tier 5 embedded-Python fallback failing
rem outright, for CI coverage of the "every tier exhausted" clean-:die path.
set "HP_TEST_FORCE_EMBED_FAIL=%HP_TEST_FORCE_EMBED_FAIL%"
rem HP_TEST_FORCE_EMBED_REAL=1: narrow test-only hole through HP_OFFLINE_MODE=1 for the embed
rem tier's own download AND the get-pip.py download it triggers (mirrors
rem HP_TEST_FORCE_VENV_CREATE_FAIL's existing exception for :download_get_pip) so CI can exercise
rem the real embed download/extract/patch/pip-bootstrap path while HP_OFFLINE_MODE=1 still blocks
rem unrelated Miniconda/uv downloads earlier in the same test run.
set "HP_TEST_FORCE_EMBED_REAL=%HP_TEST_FORCE_EMBED_REAL%"
rem HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=1: former CLAUDE.md Active Backlog item 12. Deterministically
rem fails ONLY the first embed-tier download attempt (no network touched, no real failure needed)
rem so :embed_dl_retry's genuine mid-download-failure-then-retry-once path gets CI coverage --
rem mirrors HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL's established one-shot-then-succeed pattern.
set "HP_TEST_FORCE_EMBED_DL_FAIL_ONCE=%HP_TEST_FORCE_EMBED_DL_FAIL_ONCE%"
rem HP_TEST_FORCE_CONDA_FAIL=1: simulates conda env creation failure for REQ-009/REQ-014 branch coverage
set "HP_TEST_FORCE_CONDA_FAIL=%HP_TEST_FORCE_CONDA_FAIL%"
rem HP_TEST_FORCE_WARNFIX_UNRESOLVED=1: forces the warnfix cascade-candidate detection (REQ-009/REQ-005.10) for branch coverage
set "HP_TEST_FORCE_WARNFIX_UNRESOLVED=%HP_TEST_FORCE_WARNFIX_UNRESOLVED%"
rem HP_TEST_CASCADE_ANSWER=Y|N: bypasses the cascade consent prompt (REQ-009/REQ-005.10) for CI testing
set "HP_TEST_CASCADE_ANSWER=%HP_TEST_CASCADE_ANSWER%"
rem HP_ALLOW_VENV_FALLBACK (deprecated): venv fallback is now unconditional when conda fails; accepted but ignored.
set "HP_ALLOW_VENV_FALLBACK=%HP_ALLOW_VENV_FALLBACK%"
rem HP_ALLOW_SYSTEM_FALLBACK (deprecated as a gate): system Python fallback (REQ-009 Tier 4) is now
rem reached in any run and gated solely by the REQ-014 consent prompt; this flag is accepted but
rem ignored. Conda-only mode still suppresses all non-conda fallbacks via HP_FORCE_CONDA_ONLY.
set "HP_ALLOW_SYSTEM_FALLBACK=%HP_ALLOW_SYSTEM_FALLBACK%"
rem HP_TEST_FORCE_CONSENT_CHECK=1: directly triggers consent gate at startup for REQ-014 branch coverage
set "HP_TEST_FORCE_CONSENT_CHECK=%HP_TEST_FORCE_CONSENT_CHECK%"
rem HP_TEST_SYSCON_ANSWER=Y|N: bypasses the REQ-014 system Python consent prompt for CI testing
set "HP_TEST_SYSCON_ANSWER=%HP_TEST_SYSCON_ANSWER%"
rem HP_TEST_CORRUPT_CONDA=1: simulates a corrupt conda binary for REQ-020 branch coverage (corruption hardening)
set "HP_TEST_CORRUPT_CONDA=%HP_TEST_CORRUPT_CONDA%"
rem HP_TEST_HEAL_ANSWER=Y|N: bypasses the interactive Y/N prompt in :conda_binary_corrupt for CI testing
set "HP_TEST_HEAL_ANSWER=%HP_TEST_HEAL_ANSWER%"
rem HP_TEST_CORRUPT_UV=1: simulates a corrupt uv binary; clears cache and re-downloads for REQ-020 branch coverage
set "HP_TEST_CORRUPT_UV=%HP_TEST_CORRUPT_UV%"
rem HP_TEST_FORCE_UV_FAIL=1: CI-only; forces uv acquisition to fail entirely (before any download attempt).
rem Use in justme-test lane to exercise Miniconda/JustMe paths that are bypassed when uv succeeds.
rem Mirrors HP_TEST_FORCE_CONDA_FAIL=1 pattern. Does not affect HP_TEST_UV_DL_FALLBACK behavior.
set "HP_TEST_FORCE_UV_FAIL=%HP_TEST_FORCE_UV_FAIL%"
rem HP_TEST_SKIP_EVICT=1: CI-only; skips the rmdir and Miniconda re-download in :evict_and_rebuild.
rem Use with HP_TEST_CORRUPT_CONDA=1 + HP_TEST_HEAL_ANSWER=Y to test the accept branch without
rem deleting the real CI Miniconda installation. The eviction log line is still emitted.
set "HP_TEST_SKIP_EVICT=%HP_TEST_SKIP_EVICT%"
rem HP_TEST_FORCE_PYINSTALLER_FAIL=1: CI-only; forces the PyInstaller build command itself to
rem fail (errorlevel 1) without touching the real invocation. Branch coverage for the
rem PyInstaller-build-failure -> HP_BOOTSTRAP_STATE=error path (see the "derived requirement"
rem comment right before the PyInstaller build call, inside :run_entry_smoke's else-branch,
rem for the bug this guards against).
set "HP_TEST_FORCE_PYINSTALLER_FAIL=%HP_TEST_FORCE_PYINSTALLER_FAIL%"
rem HP_TEST_FORCE_OUTPUT_VANISH=1: CI-only; deletes the freshly-built dist\<env>.exe immediately
rem after a genuinely successful PyInstaller build, before the exist-check that follows --
rem simulates AV-style post-creation removal (research Finding 2 in docs/prd-av-safe-build-path.md)
rem as a distinct scenario from the build command itself failing.
set "HP_TEST_FORCE_OUTPUT_VANISH=%HP_TEST_FORCE_OUTPUT_VANISH%"
rem HP_TEST_FORCE_NUITKA_FAIL=1: CI-only; forces the AV-Safe Build Path Tier A fallback
rem (:try_nuitka_tier_a) to fail without attempting a real Nuitka build. Used to test
rem tier-exhaustion (both PyInstaller and the fallback fail) independently of a real,
rem environment-dependent Nuitka build outcome.
set "HP_TEST_FORCE_NUITKA_FAIL=%HP_TEST_FORCE_NUITKA_FAIL%"
rem HP_SKIP_NIVISA=1: REQ-008 opt-out -- skip the NI-VISA driver install even when pyvisa/visa is detected (debugging)
set "HP_SKIP_NIVISA=%HP_SKIP_NIVISA%"
rem HP_NIVISA_WAIT_SECS=<n>: REQ-008 diagnostic -- post-install registry poll budget in seconds.
rem Default (unset) keeps the fast ~15s / 3-retry behavior for gating lanes. A dedicated non-gating
rem lane sets a large value (e.g. 2700 = 45 min) to wait out a genuinely slow NI-VISA install.
set "HP_NIVISA_WAIT_SECS=%HP_NIVISA_WAIT_SECS%"
rem HP_TEST_FORCE_CONDA_BULK_FAIL=1: simulate a non-transient conda bulk-install failure so the
rem REQ-005.3 per-package fallback fires (CI branch coverage). Consumed once in :conda_bulk_install.
set "HP_TEST_FORCE_CONDA_BULK_FAIL=%HP_TEST_FORCE_CONDA_BULK_FAIL%"
rem HP_TEST_FORCE_INTERACTIVE_PROBE=1: CI-only; forces the fail-fast probe's interactive branch
rem (:try_fast_exe / :verify_no_exe_interpreter) even under HP_CI_LANE, for deterministic branch
rem coverage of the ALIVE_AT_PROBE state machine. Mirrors HP_TEST_FORCE_PICKER.
set "HP_TEST_FORCE_INTERACTIVE_PROBE=%HP_TEST_FORCE_INTERACTIVE_PROBE%"
rem HP_FAILFAST_PROBE_MS: the fail-fast probe's classification window (default 10000ms). This is
rem NOT the same concept as the unrelated ~30s hard-kill cap used by :run_exe_smokerun /
rem :hidden_import_recover -- that is a force-kill ceiling for the fresh-build verification run
rem (the only run this bootstrapper ever kills). This probe window only decides how long to wait
rem before treating a launched process as "still alive / healthy" rather than "failed fast"; once
rem classified alive, the wait becomes unbounded and the process is never killed.
rem Widened from the original 5000ms after a real CI flake (self.failfast.probe.fastfail):
rem what races this window is not the failure itself (a raised exception unwinds and exits in
rem microseconds) but PyInstaller onefile COLD START -- extracting the bundled runtime to a temp
rem dir and booting an embedded interpreter before any user code (or its failure) can even run.
rem That step is commonly 1-3+ seconds on a healthy machine and can be pushed well past 5s under
rem CI-runner CPU/disk contention or a Defender on-access scan of the freshly-extracted EXE/DLLs.
rem A real user's own machine sees this same cold-start cost but rarely the added contention, so
rem widening this is a low-risk, low-cost change: it never introduces a kill (this window only
rem ever governs classification, never termination -- see above), and the only cost of widening
rem it is a few extra seconds before a genuinely broken cached EXE is recognized as such.
set "HP_FAILFAST_PROBE_MS=%HP_FAILFAST_PROBE_MS%"
if not defined HP_FAILFAST_PROBE_MS set "HP_FAILFAST_PROBE_MS=10000"
rem HP_TEST_CHECKPOINT_ANSWER=Y|N: bypasses the REQ-018 post-execution checkpoint prompt for CI
rem testing (mirrors HP_TEST_SYSBUILD_ANSWER/HP_TEST_SYSCON_ANSWER). Checked before HP_CI_LANE so
rem an explicit Y reaches the accept branch even in CI.
set "HP_TEST_CHECKPOINT_ANSWER=%HP_TEST_CHECKPOINT_ANSWER%"
rem HP_TEST_OPTBUILD_ANSWER=Y|N: bypasses the AV-Safe Build Path requirement 9 "want an
rem optimized build too?" prompt for CI testing (mirrors HP_TEST_CHECKPOINT_ANSWER). Checked
rem before HP_CI_LANE so an explicit Y reaches the accept branch even in CI.
set "HP_TEST_OPTBUILD_ANSWER=%HP_TEST_OPTBUILD_ANSWER%"
rem HP_TEST_FORCE_OPTBUILD_FAIL=1: CI-only; forces the requirement-9 optimized build to fail
rem deterministically (without attempting a real Nuitka build), so the "original EXE left
rem completely untouched on failure" guarantee can be tested without depending on a real,
rem environment-dependent Nuitka build outcome. Mirrors HP_TEST_FORCE_NUITKA_FAIL.
set "HP_TEST_FORCE_OPTBUILD_FAIL=%HP_TEST_FORCE_OPTBUILD_FAIL%"
rem HP_TEST_FORCE_OPTBUILD_SWAP_FAIL=1: CI-only; forces the requirement-9 post-verification
rem swap (move /y the verified temp EXE into place) to fail deterministically, by skipping the
rem real move and leaving the temp file in place -- reproducing the exact "source still exists
rem after move" signature a genuine failed swap (e.g. an AV/indexer lock on the destination)
rem would leave, without depending on an artificial OS-level file lock. Proves the original EXE
rem is left untouched and HP_NUITKA_FALLBACK_USED is never set when the swap itself fails, even
rem though the build and verification both succeeded.
set "HP_TEST_FORCE_OPTBUILD_SWAP_FAIL=%HP_TEST_FORCE_OPTBUILD_SWAP_FAIL%"
rem HP_SKIP_PEP723_WRITEBACK=1: REQ-005.11/[REQ-019] opt-out -- skip the PEP 723 header
rem write-back (uv add --script) that otherwise runs after a fresh dependency install or a
rem successful warnfix repair in uv mode. Suppression-only, per [REQ-019]: absence never
rem blocks a Prime-Directive-needed behavior; setting this only disables an optional step.
set "HP_SKIP_PEP723_WRITEBACK=%HP_SKIP_PEP723_WRITEBACK%"

rem derived requirement: CI's conda-only lane must surface conda regressions instead of masking them with opt-in fallbacks.
if "%HP_FORCE_CONDA_ONLY%"=="1" (
  rem derived requirement: conda-full diagnostics must avoid venv/system fallbacks so iterate can flag real conda regressions.
  set "HP_ALLOW_SYSTEM_FALLBACK="
  call :log "[INFO] Conda-only flag active: fallbacks disabled."
)

if not defined HP_NDJSON if exist "%CD%\tests" set "HP_NDJSON=%CD%\tests\~test-results.ndjson"
if defined HP_NDJSON (
  for %%F in ("%HP_NDJSON%") do (
    if not "%%~dpF"=="" if not exist "%%~dpF" mkdir "%%~dpF" >nul 2>&1
  )
  if not exist "%HP_NDJSON%" ( type nul > "%HP_NDJSON%" )
)
rem --- CI fast path (entry tests only) ---
call :rotate_log
rem HP_* variables represent "Helper Payload" assets emitted on demand -- now decoded
rem earlier, right before :merge_git_config, not here; see that call site's own comment.
for %%I in ("%CD%") do set "ENVNAME=%%~nI"
rem derived requirement: conda env names reject characters like '~'; self env smoke
rem scenarios run from tests\~envsmoke so normalize to ASCII word chars/_/-.
rem CLAUDE.md Item 26: '&' is special-cased to the bare word 'and' BEFORE the blanket
rem substitution below -- the blanket rule alone already avoids the real hazard (a raw '&'
rem confuses URL query-string parsing and renders oddly in Outlook), but collapses it to '_'
rem like any other stripped character, losing readability ("Sales & Marketing" -> a folder a
rem user might rename and email becoming "Sales___Marketing.exe" instead of the more legible
rem "Sales_and_Marketing.exe"). Deliberately a bare word (no surrounding underscores): the
rem existing spaces on either side of '&' are still converted to '_' by the blanket rule right
rem after, so "Sales & Marketing" -> "Sales and Marketing" -> "Sales_and_Marketing" without
rem this substitution needing to supply its own separators.
rem derived requirement: a CodeRabbit review finding on this same PR -- '&' -> 'and' is a 1-to-3
rem character expansion, so a folder name unusually heavy in '&' could make the sanitized name
rem LONGER than the original (every other stripped character before this change was a 1-to-1
rem substitution, never lengthening the result). Bounded to 64 chars post-substitution -- ample
rem for a real project folder name, well clear of Windows/conda env-name length limits for
rem everything this value later becomes (ENV_PATH, dist\<name>.exe).
set "ENVNAME_ORIG=%ENVNAME%"
set "ENVNAME_SANITIZED="
for /f "usebackq delims=" %%I in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$name = $env:ENVNAME; if (-not $name) { $name = 'env'; } $name = ($name -replace '&', 'and'); $san = ($name -replace '[^A-Za-z0-9_-]', '_'); $san = ($san -replace '^-+', '_'); if ($san.Length -gt 64) { $san = $san.Substring(0, 64).TrimEnd('_', '-') }; if ([string]::IsNullOrWhiteSpace($san) -or ($san.Trim('_', '-').Length -eq 0)) { $san = 'env'; } [Console]::Write($san)"` ) do set "ENVNAME_SANITIZED=%%I"
rem derived requirement: fail CLOSED, not open -- if the PowerShell sanitization command itself
rem errored or emitted nothing (missing powershell.exe, execution-policy lockdown, etc.), silently
rem falling through to the raw, UNSANITIZED folder name would defeat this whole guard (a leading
rem hyphen or an embedded '&' would flow straight to `conda create -n` / the exported filename).
if defined ENVNAME_SANITIZED (
  set "ENVNAME=%ENVNAME_SANITIZED%"
) else (
  call :log "[WARN] REQ-004: env-name sanitization command produced no output; falling back to 'env' for safety."
  set "ENVNAME=env"
)
set "ENVNAME_SANITIZED="
rem derived requirement: a leading hyphen is replaced above because `conda create -n -foo`
rem parses the name as a command-line flag (malformed); internal hyphens (my-app) are kept.
rem G1 guardrail: warn when folder name contained only non-word chars and defaulted to 'env'
if "%ENVNAME%"=="env" if not "%ENVNAME_ORIG%"=="env" (
  call :log "[WARN] Env name could not be derived from '%ENVNAME_ORIG%'; defaulting to 'env'."
)
set "ENVNAME_ORIG="
call :log "[INFO] Environment name: %ENVNAME%"
rem --- Host environment diagnostics (confirm runner OS/PS version for every CI run) ---
for /f "tokens=*" %%V in ('ver') do call :log "[INFO] Host OS: %%V"
for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$PSVersionTable.PSVersion.ToString()" 2^>nul`) do call :log "[INFO] Host PowerShell: %%P"

set "PYCOUNT=0"
for /f "delims=" %%F in ('dir /b /a-d *.py 2^>nul') do call :count_python "%%F"
if "%PYCOUNT%"=="" set "PYCOUNT=0"
call :log "[INFO] Python file count: %PYCOUNT%"
rem derived requirement: REQ-011 cross-dir check moved to pre-flight so users get instant
rem feedback rather than waiting through env creation to see the rejection error.
if not "%~1"=="" if /i not "%~dp1"=="%~dp0" (
  echo [ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness.
  call :log "[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder."
  call :write_status "error" 1 %PYCOUNT%
  exit /b 1
)
set "HP_CONDA_PROBE_STATUS=skipped"
set "HP_CONDA_PROBE_REASON=not-requested"

rem Slice 2b-C: compute the shared interactivity determination once, before the very first
rem :try_fast_exe call below, so both untimed user-code launch points dispatch consistently.
call :compute_interactive_run

rem --- Very top EXE fast path: reuse dist\%ENVNAME%.exe when sources are unchanged ---
set "HP_FASTPATH_USED="
rem CLAUDE.md Active Backlog Item 39 (CodeRabbit review, PR #460): defensive reset. Every real
rem write site for HP_FRESH_BUILD_OK lives inside :run_entry_smoke, whose own per-build-attempt
rem reset sits past a preflight-failure early-return (HP_PREFLIGHT_FAILED) -- a provider-cascade
rem re-entry that hits that early return would skip the in-subroutine reset. Clearing it here too,
rem before the very first :try_fast_exe call of the whole run, closes that gap unconditionally so
rem :write_fast_hash below can never fire off a value this run never genuinely earned.
set "HP_FRESH_BUILD_OK="
rem REQ-016: start clean so an inherited env var can never trigger a false "EXE
rem unverified" caveat; :run_exe_smokerun sets this only on a real non-zero EXE exit.
set "HP_EXE_VERIFY_FAILED="
rem [REQ-027] P2 honest messaging: mirrors HP_EXE_VERIFY_FAILED above, but for the NO-EXE
rem interpreter path -- :verify_no_exe_interpreter sets this only on a real non-zero
rem interpreter exit, so :print_no_exe_briefing can stop unconditionally claiming success.
set "HP_NOEXE_VERIFY_FAILED="
rem REQ-012: HP_EXE_SKIPPED records that EXE verification was skipped by request
rem (HP_SKIP_EXE_SMOKERUN) -- distinct from "failed" -- for the post-flight note.
set "HP_EXE_SKIPPED="
rem Slice 2b-C: start clean so an inherited HP_SMOKE_RC (e.g. from a parent shell/CI wrapper)
rem can never be misread as "the just-attempted run failed" below -- :try_fast_exe's own
rem REQ-012 HP_SKIP_EXE_SMOKERUN early-return leaves HP_SMOKE_RC untouched by design (no run
rem happened), and the HP_FASTPATH_RUN_FAILED check right after this call relies on that
rem meaning "empty", not "whatever happened to be inherited."
set "HP_SMOKE_RC="
if not "%PYCOUNT%"=="0" (
  call :try_fast_exe
)
rem Slice 2b-C: HP_FASTPATH_USED alone is no longer proof of a clean run -- the interactive
rem fail-fast probe can leave it set even when the reused EXE later exited non-zero (it is
rem classified alive/healthy at the probe and is never discarded/rebuilt for a later failure;
rem see :try_fast_exe). Decouple "keep the cached EXE, skip the rebuild" from "declare full
rem success" so that outcome is never silently swallowed: HP_SMOKE_RC is empty when the run
rem never happened or was skipped by request (REQ-012, HP_EXE_SKIPPED) -- still the
rem zero-friction path -- and is a real non-zero value only for a genuine post-probe failure.
set "HP_FASTPATH_RUN_FAILED="
if defined HP_SMOKE_RC if not "%HP_SMOKE_RC%"=="0" set "HP_FASTPATH_RUN_FAILED=1"
if defined HP_FASTPATH_USED (
  if defined HP_FASTPATH_RUN_FAILED (
    rem :run_failfast_probe already logged "[STATUS] Run Status: FAILED, Exit Code: ..."
    rem for this exact run -- it always fires before returning here -- add only the extra
    rem context, not a duplicate STATUS line.
    call :log "[WARN] dist\%ENVNAME%.exe (standalone EXE, PyInstaller build) ran to completion and exited non-zero after passing the fail-fast probe; treated as your program's own result, not a rebuild trigger."
  ) else (
    rem derived requirement: if the EXE fast path succeeds, treat bootstrap as complete without touching Conda/venv.
    call :log "[INFO] Fast path: skipping PyInstaller rebuild for existing dist\%ENVNAME%.exe"
  )
  if /I "%HP_BOOTSTRAP_STATE%"=="ok" (
    call :write_status ok 0 %PYCOUNT%
  ) else (
    call :write_status "%HP_BOOTSTRAP_STATE%" 0 %PYCOUNT%
  )
  goto :success
)
set "HP_FASTPATH_RUN_FAILED="

if "%PYCOUNT%"=="0" (
  rem derived requirement: CI observed the Miniconda probe firing before the
  rem empty-repo fast path, so keep this guard ahead of any network/bootstrap
  rem calls to avoid flaky failures when no Python sources exist.
  echo Python file count: %PYCOUNT%
  >> "%LOG%" echo Python file count: %PYCOUNT%
  echo No Python files detected; skipping environment bootstrap.
  >> "%LOG%" echo No Python files detected; skipping environment bootstrap.
  call :log "[INFO] No Python files detected; skipping environment bootstrap."
  call :check_hidden_ext_hint
  call :check_subfolder_hint
  call :write_status no_python_files 0 %PYCOUNT%
  goto :success
)

if defined HP_CI_SKIP_ENV goto :ci_skip_entry

rem === uv acquisition (preferred env+dep installer; falls back to conda) =======
rem derived requirement: uv is gated by HP_FORCE_CONDA_ONLY (same gate used for
rem venv/system fallbacks) so the conda-full CI lane exercises the pure conda path.
rem The binary is cached under ~uv_bin\ (tilde-prefix keeps it gitignored).
rem Orchestration layer: force uv to use only its own managed CPython toolchain and
rem ignore any ambient/legacy system or conda interpreter on PATH/registry (e.g. the
rem GitHub runner's hostedtoolcache Python). With no user version constraint uv then
rem selects the latest managed CPython; a user runtime.txt/pyproject.toml is still
rem honored via the --python forwarding applied downstream where the uv venv is created
rem (HP_UV_PY_REQ; loose constraints forward the range so uv picks the latest satisfying
rem version, exact pins stay fixed). Set before the PVW_UV_EXE branch and before the first uv
rem invocation so every uv command (run,
rem venv, pip) in this process inherits it. See docs/agent-lessons-learned.md.
set "UV_PYTHON_PREFERENCE=only-managed"
call :log "[INFO] uv: UV_PYTHON_PREFERENCE=only-managed (orchestration uses managed Python)."
if not defined PVW_UV_EXE goto :pvw_uv_exe_skip
set "HP_UV_EXE=%PVW_UV_EXE%"
call :log "[INFO] uv: using super-user override PVW_UV_EXE."
goto :uv_acquire_done
:pvw_uv_exe_skip
set "HP_UV_EXE="
set "HP_UV_BIN=%HP_SCRIPT_ROOT%~uv_bin"
set "HP_UV_ZIP=%TEMP%\~uv_setup.zip"
if "%HP_FORCE_CONDA_ONLY%"=="1" (
  call :log "[INFO] uv: skipped (HP_FORCE_CONDA_ONLY=1)."
  goto :uv_acquire_done
)
if "%HP_TEST_FORCE_UV_FAIL%"=="1" (
  call :log "[WARN] uv: HP_TEST_FORCE_UV_FAIL: simulating uv acquisition failure."
  set "UV_FALLBACK_REASON=test_forced_fail"
  call :log "[WARN] UV_FALLBACK reason=test_forced_fail"
  goto :uv_acquire_done
)
if exist "%HP_UV_BIN%\uv.exe" (
  if defined HP_TEST_CORRUPT_UV (
    call :log "[WARN] HP_TEST_CORRUPT_UV: simulating corrupt uv binary; clearing cache."
    del /f /q "%HP_UV_BIN%\uv.exe" >nul 2>&1
    goto :uv_acquire_download
  )
  "%HP_UV_BIN%\uv.exe" --version >nul 2>&1
  if errorlevel 1 (
    call :log "[WARN] Cached uv.exe failed health check; clearing and re-downloading."
    del /f /q "%HP_UV_BIN%\uv.exe" >nul 2>&1
    goto :uv_acquire_download
  )
  set "HP_UV_EXE=%HP_UV_BIN%\uv.exe"
  call :log "[INFO] uv: cached binary found at ~uv_bin\uv.exe"
  goto :uv_acquire_done
)
:uv_acquire_download
if "%HP_OFFLINE_MODE%"=="1" (
  call :log "[INFO] REQ-013: Offline mode: skipping uv download."
  set "UV_FALLBACK_REASON=offline"
  call :log "[WARN] UV_FALLBACK reason=offline"
  goto :uv_acquire_done
)
call :log "[INFO] uv: downloading to ~uv_bin..."
if not exist "%HP_UV_BIN%" mkdir "%HP_UV_BIN%" >nul 2>&1
set "HP_UV_ACTIVE_URL=%HP_UV_URL%"
if "%HP_TEST_UV_DL_FALLBACK%"=="1" if not defined HP_UV_DL_INJECTED set "HP_UV_ACTIVE_URL=https://uv-test-fail.invalid/uv-x86_64-pc-windows-msvc.zip"
call :log "[INFO] Downloading uv from %HP_UV_ACTIVE_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_UV_ACTIVE_URL%" -o "%HP_UV_ZIP%" >> "%LOG%" 2>&1
if errorlevel 1 if exist "%HP_UV_ZIP%" del "%HP_UV_ZIP%" >nul 2>&1
if not exist "%HP_UV_ZIP%" (
  if not "%HP_TEST_UV_DL_FALLBACK%"=="1" call :check_net_after_dl_fail
)
if not exist "%HP_UV_ZIP%" if not "%HP_OFFLINE_MODE%"=="1" (
  if defined HP_UV_DL_INJECTED (
    call :log "[ERROR] Injected HP_UV_URL failed; not trying fallback."
  ) else (
    call :log "[INFO] Trying fallback uv URL: %HP_UV_FALLBACK_URL%..."
    curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_UV_FALLBACK_URL%" -o "%HP_UV_ZIP%" >> "%LOG%" 2>&1
    if errorlevel 1 if exist "%HP_UV_ZIP%" del "%HP_UV_ZIP%" >nul 2>&1
    if exist "%HP_UV_ZIP%" (
      call :log "[INFO] uv download succeeded from fallback URL."
    ) else (
      call :log "[WARN] uv: all download URLs failed."
    )
  )
)
if exist "%HP_UV_ZIP%" (
  for %%S in ("%HP_UV_ZIP%") do (
    if %%~zS GEQ %HP_UV_MIN_BYTES% (
      powershell -NoProfile -ExecutionPolicy Bypass -Command "try { Expand-Archive -LiteralPath '%HP_UV_ZIP%' -DestinationPath '%HP_UV_BIN%' -Force } catch { exit 1 }" >> "%LOG%" 2>&1
    ) else (
      call :log "[WARN] uv: zip too small (%%~zS bytes); skipping extract."
    )
  )
  del "%HP_UV_ZIP%" >nul 2>&1
)
if exist "%HP_UV_BIN%\uv.exe" (
  set "HP_UV_EXE=%HP_UV_BIN%\uv.exe"
  call :log "[INFO] uv: acquired at ~uv_bin\uv.exe"
) else (
  call :log "[WARN] uv: acquisition failed; will use conda for env creation."
  set "UV_FALLBACK_REASON=acquire_failed"
  call :log "[WARN] UV_FALLBACK reason=acquire_failed"
)
:uv_acquire_done

rem === uv-first: skip Miniconda when uv can provide Python ====================
rem derived requirement: when uv is available, use it to run ~detect_python.py
rem instead of CONDA_BASE_PY so the Miniconda download can be skipped entirely.
rem HP_FORCE_CONDA_ONLY already cleared HP_UV_EXE above, so no extra check needed.
if not defined HP_UV_EXE goto :uv_first_skip
set "HP_RUNTIME_TXT_PREEXIST="
if exist "runtime.txt" set "HP_RUNTIME_TXT_PREEXIST=1"
call :emit_from_base64 "~detect_python.py" HP_DETECT_PY
if errorlevel 1 goto :uv_first_skip
"%HP_UV_EXE%" run --no-project python "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%"
if errorlevel 1 (
  call :log "[WARN] uv-first: detect_python via uv run failed; will download Miniconda."
  goto :uv_first_skip
)
set "PYSPEC="
for /f "usebackq delims=" %%A in ("~py_spec.txt") do set "PYSPEC=%%A"
set "HP_UV_PROVIDING_PYTHON=1"
call :log "[INFO] uv-first: Miniconda download skipped."
:uv_first_skip

rem === Miniconda URL probe (CI only, deferred after uv detection) ==============
rem derived requirement: probe deferred to after uv detection so that uv-first
rem runs skip the ~99 MB download when Miniconda will not be used. The probe
rem verifies the Miniconda URL is reachable only when conda is actually needed.
if "%HP_CI_TEST_CONDA_DL%"=="1" (
  if not defined HP_CI_SKIP_ENV (
    if not defined HP_UV_PROVIDING_PYTHON (
      set "HP_CONDA_PROBE_STATUS=ran"
      call :probe_conda_url
      if errorlevel 1 (
        rem derived requirement: CI observed the Miniconda probe erroring before real bootstrap.
        rem Emit a warning and continue so the actual install path can still run.
        set "HP_CONDA_PROBE_STATUS=failed"
        set "HP_CONDA_PROBE_REASON=probe-failed"
        call :log "[WARN] Miniconda download probe failed; continuing to bootstrap."
      )
    ) else (
      set "HP_CONDA_PROBE_REASON=uv-first"
    )
  ) else (
    set "HP_CONDA_PROBE_REASON=skip-env"
  )
)
if "%HP_CONDA_PROBE_STATUS%"=="skipped" (
  call :emit_conda_probe_skip
)

rem === Miniconda location (non-admin) =========================================
rem G2 guardrail: warn if PUBLIC is absent so path failures are observable
if not defined PUBLIC call :log "[WARN] PUBLIC env var not defined; Miniconda path may be invalid."
set "MC=%PUBLIC%\Documents\Miniconda3"
set "CONDA_MAIN=%MC%\condabin\conda.bat"
set "CONDA_ALT=%MC%\Scripts\conda.bat"
set "MINICONDA_ROOT=%MC%"
set "CONDA_BASE_PY=%MINICONDA_ROOT%\python.exe"

call :select_conda_bat
rem PVW_CONDA_EXE: super-user override for the conda batch file path. When set, Miniconda
rem installation is skipped. Requires conda to already be on PATH (the 'where conda' probe
rem below will fail gracefully if not). Typical usage: pointing at a system-wide conda install.
if defined PVW_CONDA_EXE set "CONDA_BAT=%PVW_CONDA_EXE%"

rem Install Miniconda if conda.bat is missing (skipped when uv is providing Python)
set "HP_CONDA_JUST_INSTALLED="
if not defined HP_UV_PROVIDING_PYTHON if not defined CONDA_BAT (
  set "HP_CONDA_JUST_INSTALLED=1"
  echo [INFO] Installing Miniconda into "%MINICONDA_ROOT%"...
  call :download_miniconda_exe
  if exist "%TEMP%\miniconda.exe" (
    REM Attempt AllUsers install with JustMe fallback; see :try_conda_install.
    call :try_conda_install
  )
  if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1
  call :select_conda_bat
)

set "PATH=%MINICONDA_ROOT%\condabin;%MINICONDA_ROOT%\Scripts;%MINICONDA_ROOT%\Library\bin;%MINICONDA_ROOT%;%PATH%"
:after_conda_bat_validation

if not defined HP_UV_PROVIDING_PYTHON if not defined CONDA_BAT (
  set "HP_ENV_READY="
  call :handle_conda_failure "conda.bat not found after bootstrap."
  if defined HP_ENV_READY goto :after_env_mode_selection
  call :die "[ERROR] conda.bat not found after bootstrap."
  goto :after_env_mode_selection
)

rem === Fresh install: warm up conda to initialize base-env state (REQ-020) ===
rem derived requirement: Silent Miniconda install (/S /AddToPath=0) leaves the base
rem environment in a state where conda info may return non-zero until conda is run
rem once with its own directories in PATH. Calling conda info here (silently, after
rem the PATH update) ensures subsequent bootstrap runs that find the pre-installed
rem Miniconda pass the corruption health check instead of falsely flagging it corrupt.
if defined HP_CONDA_JUST_INSTALLED if defined CONDA_BAT (
  call "%CONDA_BAT%" info >nul 2>&1
)

rem === Validate existing conda binary health (REQ-020: corruption hardening) ===
rem Only fires for pre-existing installs (HP_CONDA_JUST_INSTALLED guards fresh downloads).
rem Skipped when HP_TEST_FORCE_CONDA_FAIL=1 (test flag already simulates conda failure).
rem Placed after PATH update so conda.bat internal calls can resolve their dependencies.
if defined CONDA_BAT if not defined HP_CONDA_JUST_INSTALLED if not defined HP_TEST_FORCE_CONDA_FAIL (
  if defined HP_TEST_CORRUPT_CONDA (
    call :log "[ERROR] HP_TEST_CORRUPT_CONDA: simulating corrupt conda binary."
    goto :conda_binary_corrupt
  )
  call "%CONDA_BAT%" info >nul 2>&1
  if errorlevel 1 goto :conda_binary_corrupt
)
if defined HP_UV_PROVIDING_PYTHON goto :after_conda_probes
where conda >> "%LOG%" 2>&1 || (
  set "HP_ENV_READY="
  call :handle_conda_failure "[ERROR] 'conda' not found on PATH after bootstrap."
  if defined HP_ENV_READY goto :after_env_mode_selection
  call :die "[ERROR] 'conda' not found on PATH after bootstrap."
  goto :after_env_mode_selection
)
where python >> "%LOG%" 2>&1 || (
  set "HP_ENV_READY="
  call :handle_conda_failure "[ERROR] 'python' not found on PATH after bootstrap."
  if defined HP_ENV_READY goto :after_env_mode_selection
  call :die "[ERROR] 'python' not found on PATH after bootstrap."
  goto :after_env_mode_selection
)
python -V >> "%LOG%" 2>&1 || (
  set "HP_ENV_READY="
  call :handle_conda_failure "[ERROR] 'python -V' failed after bootstrap."
  if defined HP_ENV_READY goto :after_env_mode_selection
  call :die "[ERROR] 'python -V' failed after bootstrap."
  goto :after_env_mode_selection
)
:after_conda_probes

rem === Channel policy (determinism & legal) ===================================
if not defined HP_UV_PROVIDING_PYTHON if not exist "%CONDA_BAT%" (
  call :die "[ERROR] Conda not found at: %CONDA_BAT%"
  goto :after_env_mode_selection
)
if not defined HP_UV_PROVIDING_PYTHON call "%CONDA_BAT%" config --name base --add channels conda-forge >> "%LOG%" 2>&1

rem NOTE: every 'conda create' or 'conda install' call below MUST include:
rem       --override-channels -c conda-forge

set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%"
call :log "[INFO] Workspace: %CD%"
call :log "[INFO] Env name: %ENVNAME%"
call :log "[INFO] Log: %LOG%"

if not defined HP_UV_PROVIDING_PYTHON (
  set "HP_RUNTIME_TXT_PREEXIST="
  if exist "runtime.txt" set "HP_RUNTIME_TXT_PREEXIST=1"
)
rem --- Detect required Python version (must run before env-state check) ---
rem derived requirement: PYSPEC must be known before the env-state skip decision
rem so a runtime.txt / pyproject.toml change triggers a full env rebuild.
rem uv-first path: already ran detect_python via uv run and set PYSPEC above.
if defined HP_UV_PROVIDING_PYTHON goto :detect_python_done
call :emit_from_base64 "~detect_python.py" HP_DETECT_PY
if errorlevel 1 call :die "[ERROR] Could not write ~detect_python.py"
if exist "%CONDA_BASE_PY%" (
  "%CONDA_BASE_PY%" "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%"
) else (
  call "%CONDA_BAT%" run -n base python "~detect_python.py" > "~py_spec.txt" 2>> "%LOG%"
)
set "PYSPEC="
for /f "usebackq delims=" %%A in ("~py_spec.txt") do set "PYSPEC=%%A"
:detect_python_done
if defined PVW_TARGET_PY set "PYSPEC=%PVW_TARGET_PY%"
if defined PVW_TARGET_PY call :log "[INFO] Python version: using super-user override PVW_TARGET_PY."

rem --- Env-state fast path: skip conda create+install if env is still valid ---
rem derived requirement: ~env.state.json records envMode/envName/envPath/pySpec/lockSize
rem from the last successful run. Fast path fires only when PYSPEC, envName, lock size,
rem and python.exe all match the stored snapshot.
rem uv-first path: env reuse is handled by the .uv_env existence check below; skip here.
if defined HP_UV_PROVIDING_PYTHON goto :env_state_check_done
set "HP_ENV_STATE_RESULT="
call :emit_from_base64 "~env_state.py" HP_ENV_STATE
if errorlevel 1 goto :env_state_check_done
if exist "%CONDA_BASE_PY%" (
  "%CONDA_BASE_PY%" "~env_state.py" --check > "~env_state.txt" 2>> "%LOG%"
) else (
  call "%CONDA_BAT%" run -n base python "~env_state.py" --check > "~env_state.txt" 2>> "%LOG%"
)
for /f "usebackq delims=" %%E in ("~env_state.txt") do set "HP_ENV_STATE_RESULT=%%E"
if exist "~env_state.txt" del "~env_state.txt" >nul 2>&1
if exist "~env_state.py" del "~env_state.py" >nul 2>&1
if /I "%HP_ENV_STATE_RESULT%"=="skip" (
  call :log "[INFO] Env-state fast path: reusing conda env %ENVNAME%."
  goto :env_state_fast_path
)
:env_state_check_done
rem --- ENVNAME guard: default to 'env' if sanitization yielded an empty name ---
if "%ENVNAME%"=="" (
  call :log "[WARN] Conda env name resolved to empty; defaulting to 'env'."
  set "ENVNAME=env"
)
rem Recalculate ENV_PATH so it is always consistent with the guarded ENVNAME value
set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%"
rem === uv venv creation (primary path when uv was acquired) ====================
rem derived requirement: uv creates a pip-native venv at .uv_env in the project
rem folder, short-circuiting conda create. On failure, :try_conda_create runs the
rem existing conda path unchanged. REQ-004 Tier 1-2 Python version (PYSPEC) is
rem forwarded to uv via --python X.Y when a lower-bound version can be extracted.
if not defined HP_UV_EXE goto :try_conda_create
set "HP_UV_ENV_PATH=%HP_SCRIPT_ROOT%.uv_env"
if defined PVW_WORKSPACE set "HP_UV_ENV_PATH=%PVW_WORKSPACE%"
if exist "%HP_UV_ENV_PATH%\Scripts\python.exe" (
  "%HP_UV_ENV_PATH%\Scripts\python.exe" -c "import pip;exit(0)" >nul 2>&1
  if not errorlevel 1 (
    set "HP_ENV_MODE=uv"
    set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe"
    set "ENV_PATH=%HP_UV_ENV_PATH%"
    call :log "[INFO] uv: reusing existing .uv_env"
    goto :uv_venv_ready
  )
)
rem Translate PYSPEC into a uv --python request (REQ-004 Tiers 1-2, floor-vs-pin).
rem Two outputs, pipe-delimited: HP_UV_PY_REQ (forwarded to uv) and HP_UV_PY_DISP (log only).
rem  - Exact pins (python=X.Y runtime.txt, python==X.Y) -> bare "X.Y" (uv pins to X.Y).
rem  - Loose/range forms (python>=X.Y, python>X.Y, python>=X.Y,<X.Z) -> the RANGE itself, so
rem    uv resolves the LATEST satisfying managed CPython instead of the floor (matches the
rem    conda path, which hands the full range to its solver).
rem CRITICAL: the range may contain < and > . Forward it ONLY through the double-quoted
rem --python "%HP_UV_PY_REQ%" argument (quotes shield it from cmd's redirection parser).
rem The :log line uses HP_UV_PY_DISP, which is operator-free ("X.Y" or "X.Y or newer"),
rem because :log echoes its message UNQUOTED -- a raw < or > there would be a redirection.
rem Single quotes only inside -Command (a literal " would close the cmd-level quote).
rem [Console]::Write avoids the trailing CR that for /f does not strip.
set "HP_UV_PY_REQ="
set "HP_UV_PY_DISP="
if defined PYSPEC (
  for /f "usebackq tokens=1,2 delims=|" %%V in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$s = $env:PYSPEC; $req = ($s -replace '^python','').Trim(); if ($req -match '^==?([0-9].*)$') { $req = $Matches[1] }; $floor = ''; if ($s -match '([0-9]+\.[0-9]+)') { $floor = $Matches[1] }; $disp = $floor; if ($req -ne $floor) { $disp = $floor + ' or newer' }; [Console]::Write($req + '|' + $disp)"`) do (
    set "HP_UV_PY_REQ=%%V"
    set "HP_UV_PY_DISP=%%W"
  )
)
if defined HP_UV_PY_REQ (
  call :log "[INFO] uv: creating venv at .uv_env with Python %HP_UV_PY_DISP%..."
  "%HP_UV_EXE%" venv --seed --python "%HP_UV_PY_REQ%" "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1
) else (
  call :log "[INFO] uv: creating venv at .uv_env..."
  "%HP_UV_EXE%" venv --seed "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1
)
if errorlevel 1 goto :uv_venv_fail
if not exist "%HP_UV_ENV_PATH%\Scripts\python.exe" goto :uv_venv_fail
set "HP_ENV_MODE=uv"
set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe"
set "ENV_PATH=%HP_UV_ENV_PATH%"
call :log "[INFO] uv: venv created at .uv_env"
:uv_venv_ready
call :log "[INFO] HP_ENV_MODE=uv"
call :log "[BOOT] REQ-009: Selected Python provider: UV."
call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER
if not errorlevel 1 (
  "%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%"
  for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A"
  if not defined HP_RUNTIME_TXT_PREEXIST call :write_runtime_txt
)
if "%HP_TEST_UV_FAIL%"=="1" (
  call :log "[TEST] Injecting uv dep install failure"
  "%HP_UV_EXE%" pip install --python "%HP_PY%" __hp_test_nonexistent_pkg_0xdeadbeef__ >> "%LOG%" 2>&1
  call :log "[WARN] uv pip install -r requirements.txt failed; some packages may be missing."
  set "UV_FALLBACK_REASON=dep_install_failed"
  call :log "[WARN] UV_FALLBACK reason=dep_install_failed"
)
goto :after_env_mode_selection
:uv_venv_fail
rem derived requirement: uv venv reads pyproject.toml for requires-python even when
rem --python is not passed, so a malformed pyproject.toml causes venv creation to fail.
rem When HP_UV_PROVIDING_PYTHON=1 (conda not installed), retry via "uv run --no-project
rem python -m venv" which bypasses project discovery and ignores pyproject.toml.
rem The HP_PYPROJ_DEPS path (line ~712) later detects the malformed TOML and emits the
rem [WARN] pyproject.toml TOML parse error message as normal.
if defined HP_UV_PROVIDING_PYTHON (
  call :log "[WARN] uv: venv creation failed; retrying via uv run --no-project (malformed pyproject.toml guard)."
  if defined HP_UV_PY_REQ (
    "%HP_UV_EXE%" run --no-project --python "%HP_UV_PY_REQ%" python -m venv "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1
  ) else (
    "%HP_UV_EXE%" run --no-project python -m venv "%HP_UV_ENV_PATH%" >> "%LOG%" 2>&1
  )
  if not errorlevel 1 (
    if exist "%HP_UV_ENV_PATH%\Scripts\python.exe" (
      set "HP_ENV_MODE=uv"
      set "HP_PY=%HP_UV_ENV_PATH%\Scripts\python.exe"
      set "ENV_PATH=%HP_UV_ENV_PATH%"
      call :log "[INFO] uv: venv created at .uv_env via uv run --no-project fallback"
      goto :uv_venv_ready
    )
  )
  call :log "[WARN] uv: uv run --no-project venv also failed; falling back to conda create."
)
call :log "[WARN] uv: venv creation failed; falling back to conda create."
set "UV_FALLBACK_REASON=venv_create_failed"
call :log "[WARN] UV_FALLBACK reason=venv_create_failed"
set "HP_UV_EXE="
:try_conda_create
call :log "[INFO] HP_ENV_MODE=conda"
if "%HP_TEST_FORCE_CONDA_FAIL%"=="1" goto :hp_test_conda_fail
rem derived requirement: conda env create can take several minutes; emit a user-facing message
rem so the script never appears to hang silently during the longest single step.
call :log "[INFO] Creating Python environment '%ENVNAME%' -- this may take several minutes..."
rem REQ-022: transient-retry for conda create, mirroring the proven :conda_bulk_install pattern
rem (findstr-detect a transient network error, wait 15s, retry once). Goto-based dispatch on
rem purpose (see docs/agent-lessons-learned.md "Provider-cascade dispatch is goto-based on
rem purpose"): the create call + %ERRORLEVEL% capture is never nested inside a parenthesized
rem if/else block, so cmd's parse-time %VAR% expansion cannot freeze it to a stale value.
if exist "~conda_create.tmp" del "~conda_create.tmp" >nul 2>&1
if "%HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL%"=="1" goto :conda_create_test_network_fail
rem [TEST] HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL: like HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL,
rem but genuinely fails BOTH the initial attempt and the retry (not cleared after the first
rem simulated failure -- see the retry call site below), so :conda_create_failed is reached
rem through the real create/retry code path instead of the :hp_test_conda_fail bypass. Exists
rem to test CLAUDE.md Active Backlog Item 23's cascade-restore fix, which specifically needs a
rem genuine (not HP_TEST_FORCE_CONDA_FAIL-style) failure reaching :conda_create_failed during a
rem cascade re-entry.
if "%HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL%"=="1" goto :conda_create_test_network_fail
rem derived requirement: HP_PYSPEC_WRITEBACK marks a PYSPEC value that was
rem self-written moments ago by a DIFFERENT provider's own resolved interpreter
rem (see :write_runtime_txt and its two inline duplicates) -- an artifact of
rem whichever provider happened to run first, not a genuine user requirement.
rem Forcing that exact patch-level pin onto conda's own solver is unsafe: conda-
rem forge's python package availability is a wholly separate release cadence
rem from CPython's/uv's, so an exact pin like python=3.14.7 can be genuinely
rem unresolvable there even though it worked fine under uv moments earlier
rem (confirmed via real CI: self.layered_e2e.chain's uv-to-conda cascade failed
rem with PackagesNotFoundInChannelsError for exactly this reason). A REAL
rem pre-existing user pin (HP_RUNTIME_TXT_PREEXIST defined, or PYSPEC sourced
rem from pyproject.toml/PEP 723 without ever going through write-back) is left
rem untouched -- only the self-authored, write-back-derived exact pin is dropped.
rem HP_PYSPEC_ORIGINAL (snapshotted at each write-back site immediately before
rem PYSPEC was overwritten) preserves whatever constraint existed BEFORE
rem write-back fired -- a genuine pyproject.toml/PEP 723 requires-python range
rem (Tier 2) or nothing at all (Tier 3, both PYSPEC and PYSPEC_ORIGINAL empty) --
rem so a real user-authored range constraint still reaches conda's solver
rem instead of being discarded along with the bad exact pin.
set "HP_CONDA_PYSPEC_USE=%PYSPEC%"
if defined HP_PYSPEC_WRITEBACK set "HP_CONDA_PYSPEC_USE=%HP_PYSPEC_ORIGINAL%"
set "HP_CONDA_PYSPEC_SKIP="
if "%HP_CONDA_PYSPEC_USE%"=="" set "HP_CONDA_PYSPEC_SKIP=1"
if defined HP_PYSPEC_WRITEBACK if not "%HP_PYSPEC_ORIGINAL%"=="" call :log "[INFO] conda create: dropping the write-back-derived exact Python pin; using the original pre-write-back Python constraint instead."
if defined HP_PYSPEC_WRITEBACK if "%HP_PYSPEC_ORIGINAL%"=="" call :log "[INFO] conda create: dropping the write-back-derived exact Python pin; conda will resolve its own latest compatible Python instead."
rem derived requirement: PYSPEC/HP_CONDA_PYSPEC_USE can legitimately contain PEP
rem 440 range operators (>=, <, <=) once sourced from pyproject.toml's
rem requires-python -- pep440_to_conda's own output -- unquoted on this command line,
rem cmd.exe would parse a bare < or > as a real redirection operator and corrupt
rem the create command (a genuine pre-existing bug found while adding this
rem HP_PYSPEC_ORIGINAL fallback, which specifically increases how often a
rem range value reaches this exact line). Quoting protects it the same way
rem HP_UV_PY_REQ is already quoted at its own uv venv --python call site.
if defined HP_CONDA_PYSPEC_SKIP (
  call "%CONDA_BAT%" create -y -n "%ENVNAME%" python pip --override-channels -c conda-forge > "~conda_create.tmp" 2>&1
) else (
  call "%CONDA_BAT%" create -y -n "%ENVNAME%" "%HP_CONDA_PYSPEC_USE%" pip --override-channels -c conda-forge > "~conda_create.tmp" 2>&1
)
set "HP_CCREATE_RC=%ERRORLEVEL%"
goto :conda_create_have_rc
:conda_create_test_network_fail
rem [TEST] HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL: simulate a transient CondaHTTPError on the
rem first conda-create attempt only (mirrors HP_TEST_FORCE_CONDA_NETWORK_FAIL for bulk-install).
echo CondaHTTPError: HTTP 000 CONNECTION FAILED (simulated) > "~conda_create.tmp"
set "HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL="
set "HP_CCREATE_RC=1"
:conda_create_have_rc
type "~conda_create.tmp" >> "%LOG%"
if not "%HP_CCREATE_RC%"=="0" goto :conda_create_check_transient
del "~conda_create.tmp" >nul 2>&1
goto :conda_create_done
:conda_create_check_transient
findstr /i /c:"CondaHTTPError" /c:"Failed to fetch" /c:"timed out" /c:"ConnectionError" "~conda_create.tmp" >nul 2>&1
set "HP_CCREATE_TRANSIENT_RC=%ERRORLEVEL%"
del "~conda_create.tmp" >nul 2>&1
if not "%HP_CCREATE_TRANSIENT_RC%"=="0" goto :conda_create_failed
echo Conda environment creation failed -- possible network or repository issue. Retrying once...
call :log "[INFO] conda create: transient failure detected; retrying after 15s."
timeout /t 15 /nobreak >nul 2>&1
echo Retrying environment creation...
if "%HP_TEST_FORCE_CONDA_CREATE_BOTH_FAIL%"=="1" goto :conda_create_retry_forced_fail
rem Same HP_CONDA_PYSPEC_USE/HP_CONDA_PYSPEC_SKIP decision as the initial attempt
rem above -- PYSPEC/HP_PYSPEC_ORIGINAL/HP_PYSPEC_WRITEBACK are unchanged since
rem then, so both are still valid.
if defined HP_CONDA_PYSPEC_SKIP (
  call "%CONDA_BAT%" create -y -n "%ENVNAME%" python pip --override-channels -c conda-forge >> "%LOG%" 2>&1
) else (
  call "%CONDA_BAT%" create -y -n "%ENVNAME%" "%HP_CONDA_PYSPEC_USE%" pip --override-channels -c conda-forge >> "%LOG%" 2>&1
)
if not errorlevel 1 goto :conda_create_done
:conda_create_retry_forced_fail
echo *** Conda environment creation could not complete. This may be a temporary network issue.
echo *** See log file for details: ~setup.log
call :log "[WARN] conda create: retry after transient failure also failed."
:conda_create_failed
set "HP_ENV_READY="
call :handle_conda_failure "[ERROR] conda env create failed."
if defined HP_ENV_READY goto :after_env_mode_selection
rem derived requirement: a genuine conda-create failure reached via a REQ-009 cascade re-entry
rem (HP_CASCADE_SAVED_PY defined, see :provider_cascade) must gracefully keep the previous
rem working build instead of hard-failing the whole bootstrap, matching every other
rem cascade-target failure (:cascade_conda_unavailable etc.) -- see CLAUDE.md Active Backlog
rem Item 23 for the full trace of why this was previously missing. On a genuine first attempt
rem (no earlier build to fall back to), HP_CASCADE_SAVED_PY is never defined, so this check is a
rem no-op and the existing hard-failure behavior below is unchanged.
if defined HP_CASCADE_SAVED_PY goto :cascade_conda_create_failed
rem CLAUDE.md Active Backlog Item 46 (Bucket A, slice 1): :die returns via exit /b, not a
rem process halt (see docs/agent-lessons-learned.md's ":die" entry), so without this goto,
rem falling through here means EVERY tier (uv already failed earlier, conda create just
rem failed, embed/venv/system all exhausted by :handle_conda_failure above) has been tried
rem and none worked -- yet execution would still fall into :conda_create_done, which
rem unconditionally sets HP_PY to a path that provably does not exist, immediately re-detects
rem that via its own "if not exist" guard, and calls :handle_conda_failure A SECOND TIME.
rem This is not just wasted CPU: :handle_conda_failure re-attempts embed download, venv
rem creation, AND the REQ-014 system-Python consent prompt -- for a real user, this means
rem watching the same failing embed/venv attempts a second time and being asked the same
rem system-Python "keep going?" question again right after already answering it once. Skipping
rem straight to :after_env_mode_selection (the same target the already-shipped :hp_test_conda_
rem fail sibling a few thousand lines below has used since before this fix) avoids that replay
rem -- note this does not, by itself, reduce the total pause count to one: :after_env_mode_
rem selection's own "if not defined HP_PY" check (a few dozen lines below this label) has the
rem identical non-halting :die shape and is reached here since HP_PY was never set this run;
rem that is a separate, not-yet-addressed call site, tracked as a follow-up Bucket A slice.
call :die "[ERROR] conda env create failed."
goto :after_env_mode_selection
:conda_create_done

set "CONDA_PREFIX=%ENV_PATH%"
set "HP_PY=%CONDA_PREFIX%\python.exe"
rem [TEST] HP_TEST_FORCE_CONDA_MISSING_PYTHON: forces the "conda create genuinely succeeded but
rem python.exe is missing afterward" branch below through a REAL successful create (not a
rem simulated create-command failure) -- deletes the real python.exe a genuine create just
rem produced. Exists to test the OTHER call site of the CLAUDE.md Item 23 cascade-restore fix
rem (see :conda_create_failed's own comment above); selfapps_cascade_conda_create_fail.ps1's
rem missing_python scenario is the only caller.
if "%HP_TEST_FORCE_CONDA_MISSING_PYTHON%"=="1" if exist "%HP_PY%" del /f /q "%HP_PY%" >nul 2>&1
if not exist "%HP_PY%" (
  set "HP_ENV_READY="
  call :handle_conda_failure "[ERROR] python.exe missing from conda environment."
  if defined HP_ENV_READY goto :after_env_mode_selection
  if defined HP_CASCADE_SAVED_PY goto :cascade_conda_create_failed
  call :die "[ERROR] python.exe missing from conda environment."
  goto :after_env_mode_selection
)

call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER
if errorlevel 1 call :die "[ERROR] Could not write ~print_pyver.py"
"%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%"
for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A"
if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" (
  >"runtime.txt" echo %PYVER%
  if errorlevel 1 (
    call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active."
  ) else (
    call :log "[INFO] runtime.txt written: %PYVER%"
    set "HP_PYSPEC_ORIGINAL=%PYSPEC%"
    set "PYSPEC=%PYVER:python-=python=%"
    set "HP_PYSPEC_WRITEBACK=1"
  )
)

rem README.md documents the conda-forge policy for this project and why .condarc is required.
rem Emit the .condarc payload from base64 so quoting stays robust on Windows CMD.
call :emit_from_base64 "~condarc" HP_CONDARC
if errorlevel 1 (
  call :die "[ERROR] Could not stage ~condarc"
  goto :after_env_mode_selection
)
if not exist "%ENV_PATH%" mkdir "%ENV_PATH%"
copy /y "~condarc" "%ENV_PATH%\.condarc" >> "%LOG%" 2>&1
if errorlevel 1 call :die "[ERROR] Could not write %ENV_PATH%\.condarc"
call :log "[BOOT] REQ-009: Selected Python provider: Conda (Portable)."
goto :after_env_mode_selection

:env_state_fast_path
rem derived requirement: env exists from prior run; set interpreter, skip create+install.
set "CONDA_PREFIX=%ENV_PATH%"
set "HP_PY=%CONDA_PREFIX%\python.exe"
if not exist "%HP_PY%" (
  call :log "[WARN] Env-state fast path: %HP_PY% not found; falling back to full env rebuild."
  set "HP_ENV_STATE_RESULT=stale"
  goto :env_state_check_done
)
call :log "[BOOT] REQ-009: Selected Python provider: Conda (Portable) [fast path]."
call :emit_from_base64 "~print_pyver.py" HP_PRINT_PYVER
if not errorlevel 1 (
  "%HP_PY%" "~print_pyver.py" > "~pyver.txt" 2>> "%LOG%"
  for /f "usebackq delims=" %%A in ("~pyver.txt") do set "PYVER=%%A"
  if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" (
    >"runtime.txt" echo %PYVER%
    if errorlevel 1 (
      call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active."
    ) else (
      call :log "[INFO] runtime.txt written: %PYVER%"
      set "HP_PYSPEC_ORIGINAL=%PYSPEC%"
      set "PYSPEC=%PYVER:python-=python=%"
      set "HP_PYSPEC_WRITEBACK=1"
    )
  )
)

:after_env_mode_selection
rem derived requirement: this label is the shared re-entry convergence point for every REQ-009
rem provider-selection success (first-time AND cascade-driven, see docs/agent-interconnect.md
rem "Provider cascade execution re-enters env-create") -- clear the cascade save/restore slot
rem here so a LATER pass's cascade decline (falling through to :after_cascade_decision without
rem ever re-entering :provider_cascade) does not restore a now-stale HP_PY from an earlier,
rem already-successful cascade.
set "HP_CASCADE_SAVED_PY="
if defined PVW_PYTHON_EXE set "HP_PY=%PVW_PYTHON_EXE%"
if defined PVW_PYTHON_EXE call :log "[INFO] Python host: using super-user override PVW_PYTHON_EXE."
rem === Conda base periodic update (~30 days) ====================================
rem derived requirement: README.md requires periodic conda base update; skip on
rem first install (timestamp seeded) and when uv env is in use.
call :conda_base_update
rem === end conda base update ====================================================
call :emit_from_base64 "~prep_requirements.py" HP_PREP_REQUIREMENTS
if errorlevel 1 call :die "[ERROR] Could not write ~prep_requirements.py"
set "REQ=requirements.txt"
if exist "%REQ%" ( for %%S in ("%REQ%") do if %%~zS EQU 0 del "%REQ%" )
if exist "%REQ%" (
  echo *** [INFO] Using requirements.txt for dependencies
  echo *** [INFO] Dependency accuracy depends on file correctness
  set "DEP_SOURCE=requirements.txt"
  set "DEP_LAYER_REQUIREMENTS=1"
  call :log "[INFO] DEP_LAYER_REQUIREMENTS=1"
  call :log "[TRACE] dep source selected: requirements.txt"
)
set "HP_JOB_SUMMARY=~pipreqs.summary.txt"
if exist "%HP_JOB_SUMMARY%" del "%HP_JOB_SUMMARY%"
if not defined HP_PY (
  set "HP_NO_INTERPRETER=1"
  call :die "[ERROR] Active Python interpreter not resolved."
)

echo Interpreter: %HP_PY%
>> "%LOG%" echo Interpreter: %HP_PY%
call :append_env_mode_row
"%HP_PY%" -c "print('py_ok')" 1>nul 2>nul || (call :log "[WARN] Interpreter smoke test failed (continuing)." & set "HP_NO_INTERPRETER=1")
"%HP_PY%" -c "import sys;print(sys.version.split()[0])" > "~pyver_host.tmp" 2>nul
if exist "~pyver_host.tmp" for /f "usebackq delims=" %%Y in ("~pyver_host.tmp") do call :log "[INFO] Host Python: %%Y"
if exist "~pyver_host.tmp" del "~pyver_host.tmp" >nul 2>&1
set "PEP723_ACTIVE="
set "PEP723_BLOCK_FOUND="
set "PEP723_REQ=~requirements.pep723.txt"
if exist "%PEP723_REQ%" del "%PEP723_REQ%" >nul 2>&1
call :determine_entry "%~1"
if errorlevel 11 (
  call :write_status "error" 1 %PYCOUNT%
  exit /b 1
)
if errorlevel 1 (
  call :die "[ERROR] Could not determine entry point"
  goto :after_env_bootstrap
)
rem derived requirement: do NOT interpolate %HP_APP_ARGS% into this message -- :log echoes
rem UNQUOTED, and a user-supplied argument could contain < > | & (docs/agent-lessons-learned.md
rem ":log echoes UNQUOTED"). This confirms forwarding is active without echoing arg content.
if defined HP_APP_ARGS call :log "[INFO] REQ-026: extra launch argument(s) detected; they will be forwarded to your program at every launch."
rem === REQ-005.13 (Tier 2, docs/plan-autopep723-two-tier.md): HP_PVW_KNOWN_IDEMPOTENT ===
rem Opt-in execute-mode dependency discovery: the user has explicitly declared their entry
rem script safe to run more than once, so actually run it via uvx autopep723 (which persists
rem what it needed into the entry file's own PEP 723 header on success). Never gates the
rem Prime Directive: absence of the flag leaves this whole block a no-op. uv lane only.
set "HP_UVX_EXE=%HP_UV_EXE:uv.exe=uvx.exe%"
if defined HP_PVW_KNOWN_IDEMPOTENT if /I "%HP_ENV_MODE%"=="uv" if defined HP_ENTRY if exist "%HP_UVX_EXE%" (
  call :pvw_known_idempotent_run
)
set "HP_PYPROJ_REQ=~requirements.pyproject.txt"
if exist "%HP_PYPROJ_REQ%" del "%HP_PYPROJ_REQ%" >nul 2>&1
set "HP_PYPROJ_ACTIVE="
if exist "pyproject.toml" (
  call :emit_from_base64 "~pyproj_deps.py" HP_PYPROJ_DEPS
  if not errorlevel 1 (
    "%HP_PY%" "~pyproj_deps.py" "%HP_PYPROJ_REQ%" >nul 2>&1
    if errorlevel 1 (
      rem derived requirement: check the highest threshold first -- "if errorlevel N" is a
      rem GEQ N test, so errorlevel 3 -- pyproj_deps.py's own unexpected-internal-error exit,
      rem distinct from its deliberate exit 1 "nothing to do here" -- would also satisfy
      rem "if errorlevel 2" and get mislabeled as a TOML parse error if checked second.
      if errorlevel 3 (
        >> "%LOG%" echo pyproj_deps.py: unexpected internal error, exit 3; falling back to requirements.txt or pipreqs.
      ) else if errorlevel 2 (
        echo *** [WARN] pyproject.toml could not be parsed as valid TOML; falling back to requirements.txt or pipreqs.
        call :log "[WARN] pyproject.toml TOML parse error; falling back."
      )
    ) else (
      if exist "%HP_PYPROJ_REQ%" for %%S in ("%HP_PYPROJ_REQ%") do if %%~zS GTR 0 set "HP_PYPROJ_ACTIVE=1"
    )
    if exist "~pyproj_deps.py" del "~pyproj_deps.py" >nul 2>&1
  )
)
if defined HP_PYPROJ_ACTIVE (
  echo *** [INFO] pyproject.toml [project].dependencies found; overrides requirements.txt
  call :log "[INFO] pyproject.toml [project].dependencies detected"
  call :log "[INFO] DEP_SOURCE=pyproject"
  call :log "[INFO] DEP_LAYER_PYPROJECT=1"
  call :log "[TRACE] dep source selected: pyproject"
  set "DEP_SOURCE=pyproject"
  set "DEP_LAYER_PYPROJECT=1"
  copy /y "%HP_PYPROJ_REQ%" "requirements.txt" >nul 2>&1
)
if exist "%HP_PYPROJ_REQ%" del "%HP_PYPROJ_REQ%" >nul 2>&1
set "HP_PYPROJ_REQ="
set "HP_PYPROJ_ACTIVE="
if defined HP_ENTRY if exist "%HP_ENTRY%" (
  findstr /c:"# /// script" "%HP_ENTRY%" >nul 2>&1
  if not errorlevel 1 (
    set "PEP723_BLOCK_FOUND=1"
    echo *** PEP 723 metadata detected
    call :extract_pep723_requirements "%HP_ENTRY%" "%PEP723_REQ%"
    if exist "%PEP723_REQ%" for %%S in ("%PEP723_REQ%") do if %%~zS GTR 0 set "PEP723_ACTIVE=1"
  )
)
if defined PEP723_BLOCK_FOUND if not defined PEP723_ACTIVE (
  echo *** [WARN] PEP 723 block found but dependency list is empty or malformed; falling back
  call :log "[WARN] PEP 723 block found but no valid dependencies extracted; pipreqs fallback."
)
set "PEP723_BLOCK_FOUND="

set "HP_PIPREQS_INSTALL_PASS=0"
set "HP_PIPREQS_INSTALL_ATTEMPTED=0"
if not defined HP_SKIP_PIPREQS if not defined PEP723_ACTIVE (
  set "HP_PIPREQS_INSTALL_ATTEMPTED=1"
  if "%HP_ENV_MODE%"=="uv" (
    rem derived requirement: uv pip install bypasses python -m pip so pip need not
    rem be a module inside the uv venv; uv's own resolver handles the installation.
    "%HP_UV_EXE%" pip install --python "%HP_PY%" -q pipreqs==%HP_PIPREQS_VERSION% >> "%LOG%" 2>&1
  ) else (
    "%HP_PY%" -m pip install -q --disable-pip-version-check pipreqs==%HP_PIPREQS_VERSION% >> "%LOG%" 2>&1
  )
  if errorlevel 1 (
    call :log "[WARN] pipreqs install failed (Python version incompatible with pipreqs). Fallback: warnfix will detect and install missing imports at build time. Consider adding requirements.txt or pyproject.toml [project].dependencies for explicit dependency specification."
    set "HP_SKIP_PIPREQS=1"
    set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs unavailable for this Python version)"
  ) else (
    set "HP_PIPREQS_INSTALL_PASS=1"
    call :log "[INFO] pipreqs %HP_PIPREQS_VERSION% installed successfully; using it for dependency discovery."
  )
)
if defined HP_NDJSON (
  rem Emit pass=true for intentional skips -- PEP 723 or pre-existing HP_SKIP_PIPREQS --
  rem pass=true for successful installs, pass=false for install failures.
  rem Use HP_PIPREQS_INSTALL_ATTEMPTED to distinguish failed install, attempted=1 pass=0,
  rem from intentional skip, attempted=0.
  if defined PEP723_ACTIVE (
    powershell -NoProfile -ExecutionPolicy Bypass -Command ^
      "$row = @{ id='pipreqs.install'; pass=$true; reason='pep723_active' } | ConvertTo-Json -Compress -Depth 8;" ^
      "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
  ) else (
    powershell -NoProfile -ExecutionPolicy Bypass -Command ^
      "$attempted = [Environment]::GetEnvironmentVariable('HP_PIPREQS_INSTALL_ATTEMPTED') -eq '1';" ^
      "$pass = [Environment]::GetEnvironmentVariable('HP_PIPREQS_INSTALL_PASS') -eq '1';" ^
      "if ($attempted) { $reason = if ($pass) { 'success' } else { 'install_failed' } } else { $reason = 'skip_preexisting' };" ^
      "$pass = if ($attempted) { $pass } else { $true };" ^
      "$row = @{ id='pipreqs.install'; pass=$pass; reason=$reason } | ConvertTo-Json -Compress -Depth 8;" ^
      "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
  )
)

set "HP_PIPREQS_TARGET_WORK=%CD%\requirements.auto.txt"
set "HP_PIPREQS_TARGET=%HP_PIPREQS_TARGET_WORK%"
set "HP_PIPREQS_IGNORE=.git,.github,.venv,venv,env,.uv_env,build,dist,__pycache__,tests"
set "HP_PIPREQS_IGNORE_DISPLAY="
if defined HP_PIPREQS_IGNORE set "HP_PIPREQS_IGNORE_DISPLAY= --ignore \"%HP_PIPREQS_IGNORE%\""
set "HP_PIPREQS_SUMMARY_PHASE="
set "HP_PIPREQS_SUMMARY_NOTE="
set "HP_PIPREQS_SUMMARY_CMD_PATH=%HP_PIPREQS_TARGET_WORK%"
set "HP_PIPREQS_SUMMARY_IGNORE=%HP_PIPREQS_IGNORE_DISPLAY%"
set "HP_PIPREQS_PHASE_RESULT="
set "HP_PIPREQS_LAST_LOG="
set "HP_PIPREQS_DIRECT_LOG=~pipreqs_direct.log"
if exist "%HP_PIPREQS_DIRECT_LOG%" del "%HP_PIPREQS_DIRECT_LOG%"
set "HP_PIPREQS_STAGE_LOG=~pipreqs_stage.log"
if exist "%HP_PIPREQS_STAGE_LOG%" del "%HP_PIPREQS_STAGE_LOG%"
set "HP_PIPREQS_STAGE_COPY_LOG=~pipreqs_stage_copy.log"
if exist "%HP_PIPREQS_STAGE_COPY_LOG%" del "%HP_PIPREQS_STAGE_COPY_LOG%"

set "HP_PIPREQS_CANON=pipreqs . --force --mode compat --savepath requirements.auto.txt"
set "HP_PIPREQS_CMD_LOG=pipreqs . --force --mode compat --savepath \"%HP_PIPREQS_TARGET%\"%HP_PIPREQS_IGNORE_DISPLAY%"
call :log "[INFO] pipreqs (direct) command: %HP_PIPREQS_CMD_LOG%"
echo Pipreqs command (direct): %HP_PIPREQS_CMD_LOG%

if defined HP_SKIP_PIPREQS (
  set "HP_PIPREQS_PHASE_RESULT=skipped"
  set "HP_PIPREQS_SUMMARY_PHASE=skipped"
  set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped for %HP_ENV_MODE% mode)"
  set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%"
  goto :after_pipreqs_run
)
if defined PEP723_ACTIVE (
  echo *** Using dependencies from PEP 723 metadata
  echo *** [INFO] Using PEP 723 inline dependency metadata
  echo *** [INFO] Dependency accuracy depends on script metadata correctness
  call :log "[INFO] Using PEP 723 inline dependency metadata"
  call :log "[INFO] DEP_SOURCE=pep723"
  call :log "[INFO] PEP723_USED=1"
  call :log "[INFO] DEP_LAYER_PEP723=1"
  call :log "[TRACE] dep source selected: pep723"
  set "DEP_SOURCE=pep723"
  set "DEP_LAYER_PEP723=1"
  copy /y "%PEP723_REQ%" "requirements.txt" >nul 2>&1
  if errorlevel 1 call :die "[ERROR] Could not stage PEP 723 requirements."
  copy /y "%PEP723_REQ%" "requirements.auto.txt" >nul 2>&1
  set "HP_PIPREQS_PHASE_RESULT=skipped"
  set "HP_PIPREQS_SUMMARY_PHASE=skipped"
  set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped: PEP 723 metadata)"
  set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%"
  goto :after_pipreqs_run
)
rem REQ-005: Only warn when no user-provided dep source was detected (no requirements.txt /
rem pyproject / PEP 723). If DEP_SOURCE is already set, pipreqs runs as an augmentation
rem pass but the user has explicit deps -- the WARN is misleading and must be suppressed.
if not defined DEP_SOURCE (
  echo *** [WARN] Dependencies were auto-detected via pipreqs
  echo *** [WARN] Auto-detection may be incomplete or incorrect
  echo *** [INFO] Consider adding requirements.txt or PEP 723 metadata for reliability
  set "DEP_SOURCE=pipreqs"
) else (
  call :log "[TRACE] pipreqs augmenting %DEP_SOURCE% dep source; auto-detect WARN suppressed."
)

rem pipreqs invocation: uses "python -m pipreqs.pipreqs" NOT the console script (pipreqs command).
rem derived requirement: bootstrap determinism. The console script (pipreqs) relies on PATH being set
rem correctly after conda env activation, which is not guaranteed in the same shell session immediately
rem after environment creation. Using explicit Python interpreter + module bypasses PATH resolution and
rem works reliably in bootstrap contexts where shell state / PATH propagation is not fully initialized.
rem This is NOT a pipreqs API issue (the console script is the official API); it is a Windows batch
rem bootstrap sequencing issue. pipreqs is pinned to 0.4.13 permanently, so internal coupling is a
rem low-risk controlled assumption due to the pinned dependency version.
rem pipreqs flags are locked by CI (pipreqs.flags gate).
rem Rationale: compat mode for deterministic output; force overwrite; write to requirements.auto.txt (separate from committed requirements).
"%HP_PY%" -m pipreqs.pipreqs . --force --mode compat --savepath "%HP_PIPREQS_TARGET%" --ignore "%HP_PIPREQS_IGNORE%" > "%HP_PIPREQS_DIRECT_LOG%" 2>&1
:pipreqs_direct_done
rem CLAUDE.md Item 51: capture %errorlevel% on the line immediately after the pipreqs
rem invocation, before any intervening "set", matching the staging path's own already-safe
rem pattern a few dozen lines below (which never had an intervening command). A successful
rem plain "set VAR=literal" does not itself modify ERRORLEVEL in real cmd.exe, so this was
rem very likely never a live bug -- but the two call sites disagreeing on ordering, for no
rem reason, is what made this worth a live-cmd.exe check in the first place. Zero-risk either
rem way; closes the inconsistency for good.
set "HP_PIPREQS_RC=%errorlevel%"
set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_DIRECT_LOG%"
if "%HP_PIPREQS_RC%"=="0" if exist "%HP_PIPREQS_TARGET_WORK%" (
  rem Zero imports are valid: pipreqs exits 0 and may intentionally leave requirements.auto.txt empty.
  set "HP_PIPREQS_PHASE_RESULT=ok"
  set "HP_PIPREQS_SUMMARY_PHASE=direct"
  goto :after_pipreqs_run
)
rem ---- pipreqs result handling -------------------------------------------
rem RC=0  + file exists             -> OK  (deps found, or zero deps via exit 0)
rem RC!=0 + no file                 -> OK  (no imports: zero requirements)
rem RC!=0 + file empty (size=0)     -> OK  (no imports: zero requirements)
rem RC!=0 + file populated          -> FAIL (unexpected; fall through to staging)
rem
rem Notes:
rem - pipreqs returns non-zero when no imports are found: that is NOT an error
rem - We inspect file state, not just exit code, to distinguish zero-deps from crash
rem - If pipreqs crashed on a file WITH imports, output is absent/empty and this
rem   guard treats it as zero-requirements; downstream pip/tokenFound will expose it
rem - Staging path below acts as fallback for direct path quoting/path issues
rem -------------------------------------------------------------------------
rem Debug: log rc + file size so future diagnosis is trivial
set "HP_PIPREQS_SIZE=missing"
if exist "%HP_PIPREQS_TARGET_WORK%" for %%A in ("%HP_PIPREQS_TARGET_WORK%") do set "HP_PIPREQS_SIZE=%%~zA"
call :log "[DEBUG] pipreqs (direct) rc=%HP_PIPREQS_RC% size=%HP_PIPREQS_SIZE%"
rem Zero-requirements guard: accept non-zero exit only when output is absent or empty
if not "%HP_PIPREQS_RC%"=="0" (
  if not exist "%HP_PIPREQS_TARGET_WORK%" (
    set "HP_PIPREQS_PHASE_RESULT=ok"
    set "HP_PIPREQS_SUMMARY_PHASE=direct"
    set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)"
    goto :after_pipreqs_run
  ) else (
    for %%A in ("%HP_PIPREQS_TARGET_WORK%") do if %%~zA EQU 0 (
      set "HP_PIPREQS_PHASE_RESULT=ok"
      set "HP_PIPREQS_SUMMARY_PHASE=direct"
      set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)"
      goto :after_pipreqs_run
    )
  )
)

set "HP_TEMP_ROOT=%RUNNER_TEMP%"
if not defined HP_TEMP_ROOT set "HP_TEMP_ROOT=%TEMP%"
set "HP_PIPREQS_STAGE_ROOT=%HP_TEMP_ROOT%\pipreqs_stage"
set "HP_PIPREQS_STAGE_TARGET=%HP_PIPREQS_STAGE_ROOT%\requirements.auto.txt"
if exist "%HP_PIPREQS_STAGE_ROOT%" rd /s /q "%HP_PIPREQS_STAGE_ROOT%"
mkdir "%HP_PIPREQS_STAGE_ROOT%" >nul 2>&1
robocopy . "%HP_PIPREQS_STAGE_ROOT%" /E /NFL /NDL /NJH /NJS /NP ^
  /XD .git .github .venv venv env build dist __pycache__ tests ^
  /XF ~*.py > "%HP_PIPREQS_STAGE_COPY_LOG%" 2>&1
set "HP_PIPREQS_STAGE_COPY_RC=%errorlevel%"
if %HP_PIPREQS_STAGE_COPY_RC% GEQ 8 (
  set "HP_PIPREQS_PHASE_RESULT=fail"
  set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_STAGE_COPY_LOG%"
  set "HP_PIPREQS_SUMMARY_NOTE=(robocopy staging failed)"
  goto :after_pipreqs_run
)
set "HP_PIPREQS_SUMMARY_CMD_PATH=%HP_PIPREQS_STAGE_TARGET%"
set "HP_PIPREQS_SUMMARY_IGNORE="
if not exist "%HP_PIPREQS_STAGE_ROOT%\" (
    echo [WARN] pushd skipped, stage root missing: %HP_PIPREQS_STAGE_ROOT%
    set "HP_PIPREQS_PHASE_RESULT=fail"
    set "HP_PIPREQS_SUMMARY_NOTE=(stage root missing)"
    goto :after_pipreqs_run
)
pushd "%HP_PIPREQS_STAGE_ROOT%" >nul 2>&1
if errorlevel 1 (
    set "HP_PIPREQS_PHASE_RESULT=fail"
    set "HP_PIPREQS_SUMMARY_NOTE=(pushd to staging root failed)"
    goto :after_pipreqs_run
)
call :log "[INFO] pipreqs (staging) command: pipreqs . --force --mode compat --savepath ""%HP_PIPREQS_STAGE_TARGET%"""
echo Pipreqs command (staging): pipreqs . --force --mode compat --savepath "%HP_PIPREQS_STAGE_TARGET%"
:: pipreqs flags are locked by CI (pipreqs.flags gate).
:: Rationale: compat mode for deterministic output; force overwrite; write to requirements.auto.txt (separate from committed requirements).
"%HP_PY%" -m pipreqs.pipreqs . --force --mode compat --savepath "%HP_PIPREQS_STAGE_TARGET%" > "%HP_PIPREQS_STAGE_LOG%" 2>&1
set "HP_PIPREQS_RC=%errorlevel%"
popd >nul 2>&1
if errorlevel 1 call :log "[WARN] pipreqs staging: popd failed; CWD may not be restored."
set "HP_PIPREQS_LAST_LOG=%HP_PIPREQS_STAGE_LOG%"
if "%HP_PIPREQS_RC%"=="0" if exist "%HP_PIPREQS_STAGE_TARGET%" (
  rem Zero imports are valid: copy the staging file even when pipreqs produced an empty requirements list.
  copy /y "%HP_PIPREQS_STAGE_TARGET%" "%HP_PIPREQS_TARGET_WORK%" >nul 2>&1
  if errorlevel 1 (
    set "HP_PIPREQS_PHASE_RESULT=fail"
    set "HP_PIPREQS_SUMMARY_NOTE=(failed to copy staging output)"
    goto :after_pipreqs_run
  )
  set "HP_PIPREQS_PHASE_RESULT=ok"
  set "HP_PIPREQS_SUMMARY_PHASE=staging"
  set "HP_PIPREQS_SUMMARY_NOTE=(fallback after direct failure)"
  goto :after_pipreqs_run
)
rem Debug: log rc + file size for staging path
set "HP_PIPREQS_SIZE=missing"
if exist "%HP_PIPREQS_STAGE_TARGET%" for %%A in ("%HP_PIPREQS_STAGE_TARGET%") do set "HP_PIPREQS_SIZE=%%~zA"
call :log "[DEBUG] pipreqs (staging) rc=%HP_PIPREQS_RC% size=%HP_PIPREQS_SIZE%"
rem Zero-requirements guard for staging path
if not "%HP_PIPREQS_RC%"=="0" (
  if not exist "%HP_PIPREQS_STAGE_TARGET%" (
    set "HP_PIPREQS_PHASE_RESULT=ok"
    set "HP_PIPREQS_SUMMARY_PHASE=staging"
    set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)"
    goto :after_pipreqs_run
  ) else (
    for %%A in ("%HP_PIPREQS_STAGE_TARGET%") do if %%~zA EQU 0 (
      set "HP_PIPREQS_PHASE_RESULT=ok"
      set "HP_PIPREQS_SUMMARY_PHASE=staging"
      set "HP_PIPREQS_SUMMARY_NOTE=(zero requirements: no imports found)"
      goto :after_pipreqs_run
    )
  )
)
if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(staging pipreqs failed)"
set "HP_PIPREQS_PHASE_RESULT=fail"

:after_pipreqs_run
set "DEP_FINAL_COUNT=0"
if exist "requirements.txt" for /f "usebackq eol=# tokens=*" %%L in ("requirements.txt") do if not "%%L"=="" set /a DEP_FINAL_COUNT+=1
call :log "[INFO] DEP_RESOLUTION_STRATEGY=layered"
call :log "[INFO] DEP_FINAL_COUNT=%DEP_FINAL_COUNT%"
set "DEP_FINAL_COUNT="
if exist "%HP_PIPREQS_STAGE_ROOT%" rd /s /q "%HP_PIPREQS_STAGE_ROOT%"
set "HP_PIPREQS_TARGET=%HP_PIPREQS_TARGET_WORK%"
if "%HP_PIPREQS_PHASE_RESULT%"=="ok" (
  call :write_pipreqs_summary
) else (
  if /I "%HP_PIPREQS_PHASE_RESULT%"=="skipped" (
    if not defined HP_PIPREQS_SUMMARY_PHASE set "HP_PIPREQS_SUMMARY_PHASE=skipped"
    if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs skipped)"
    call :write_pipreqs_summary
  ) else (
    set "HP_PIPREQS_SUMMARY_PHASE=failed"
    if not defined HP_PIPREQS_SUMMARY_NOTE set "HP_PIPREQS_SUMMARY_NOTE=(pipreqs run failed)"
    set "HP_PIPREQS_FAILURE_LOG=%HP_PIPREQS_LAST_LOG%"
    call :write_pipreqs_summary
    rem G3 guardrail: pipreqs is discovery only; a failed scan must not block bootstrap.
    call :log "[WARN] pipreqs generation failed; continuing without auto-detected requirements."
  )
)
if not exist "%REQ%" if exist "requirements.auto.txt" (
  copy /y "requirements.auto.txt" "requirements.txt" >> "%LOG%" 2>&1
  if errorlevel 1 (
    echo *** Could not generate requirements.txt. Continuing without dependencies...
    call :log "[WARN] Failed to copy requirements.auto.txt to requirements.txt; continuing without dependency installation."
  )
)
echo (no diff: requirements files not both present) > "~pipreqs.diff.txt"
if exist "requirements.txt" if exist "requirements.auto.txt" (
  fc "requirements.txt" "requirements.auto.txt" > "~pipreqs.diff.txt" 2>&1
  rem derived requirement: use fc.exe's own exit code -- 0=identical, 1=differ, 2=comparison
  rem error -- not a findstr match on its English "FC: no differences encountered" message --
  rem that text is localized on non-English Windows, so a findstr match would never fire there
  rem and the diff would incorrectly show even for two identical files. Check errorlevel 2
  rem before errorlevel 1 -- "if errorlevel N" matches ERRORLEVEL GEQ N, so the higher value
  rem must be checked first or it would never be reached.
  if errorlevel 2 (
    call :log "[WARN] fc comparison of requirements.txt vs requirements.auto.txt reported an error; skipping diff display."
  ) else if errorlevel 1 (
    echo [INFO] requirements.txt differs from the auto-detected dependency scan; details below:
    type "~pipreqs.diff.txt"
  )
)
call :log "[INFO] REQ-005.5: dependency source diff computed -- ~pipreqs.diff.txt"
rem === REQ-005.12 (Tier 1, docs/plan-autopep723-two-tier.md): autopep723 discovery ===
rem Non-gating, additive-only augmentation of pipreqs's own discovery, uv lane only for v1.
rem autopep723 runs via uvx (isolated tool venv) rather than a direct interpreter -- see
rem docs/agent-lessons-learned.md's autopep723 environment-leak section for why a direct
rem invocation would silently under-report already-installed packages, and uvx does not.
rem A failed/missing/skipped autopep723 result is a silent no-op; pipreqs's own results
rem and the existing dependency-install path below are entirely unaffected either way.
set "HP_UVX_EXE=%HP_UV_EXE:uv.exe=uvx.exe%"
if /I "%HP_ENV_MODE%"=="uv" if defined HP_ENTRY if exist "%HP_UVX_EXE%" if not defined HP_SKIP_AUTOPEP_DISCOVERY (
  "%HP_UVX_EXE%" autopep723 check "%HP_ENTRY%" > "requirements.autopep.txt" 2>>"%LOG%"
  call :emit_from_base64 "~autopep_merge.py" HP_AUTOPEP_MERGE
  if not errorlevel 1 (
    "%HP_PY%" "~autopep_merge.py" "requirements.autopep.txt" "requirements.txt" >> "%LOG%" 2>&1
    if not errorlevel 1 (
      call :log "[INFO] REQ-005.12: autopep723 discovery merge complete."
    ) else (
      call :log "[WARN] REQ-005.12: autopep723 merge helper failed; continuing with pipreqs-only results."
    )
    if exist "~autopep_merge.py" del "~autopep_merge.py" >nul 2>&1
  )
)
rem --- Dep-check fast path: skip conda install when all pipreqs packages are in the lock ---
rem derived requirement: skip the slow conda solver on repeat runs when the
rem environment lock file already contains every package pipreqs detected.
rem goto is used to avoid %errorlevel% parse-time expansion inside parenthesized blocks.
set "HP_DEP_SKIP="
set "HP_DEP_RESULT="
rem REQ-005.11: reset on every entry to this label (including REQ-009 provider-cascade
rem re-entry) so a stale HP_UV_INSTALL_OK from a previous, now-abandoned uv attempt can
rem never satisfy :pep723_writeback's confirmed-installed gate for an unrelated trigger.
set "HP_UV_INSTALL_OK="
if not "%HP_ENV_MODE%"=="conda" if not "%HP_ENV_MODE%"=="uv" goto :dep_check_done
call :emit_from_base64 "~dep_check.py" HP_DEP_CHECK
if errorlevel 1 goto :dep_check_done
"%HP_PY%" "~dep_check.py" > "~dep_check.txt" 2>> "%LOG%"
set "HP_DEP_RC=%errorlevel%"
for /f "usebackq delims=" %%D in ("~dep_check.txt") do set "HP_DEP_RESULT=%%D"
if exist "~dep_check.txt" del "~dep_check.txt" >nul 2>&1
if exist "~dep_check.py" del "~dep_check.py" >nul 2>&1
if not "%HP_DEP_RC%"=="0" goto :dep_check_done
if /I "%HP_DEP_RESULT%"=="skip" set "HP_DEP_SKIP=1"
if defined HP_DEP_SKIP call :log "[INFO] Dep-check: all pipreqs packages satisfied in lock; skipping conda install."
:dep_check_done
rem IMPORTANT: requirements.txt must always reflect the final dependency set passed to installer.
rem All source layers (PEP 723, pyproject, pipreqs) overwrite requirements.txt in-place above.
rem Do not bypass requirements.txt without updating the snapshot and installed-state logic below.
rem --- Snapshot resolved dependency input before install ---
if exist "~dependency_resolved.txt" del "~dependency_resolved.txt" >nul 2>&1
if exist "requirements.txt" (
  copy /y "requirements.txt" "~dependency_resolved.txt" >nul 2>&1
  if not errorlevel 1 call :log "[INFO] DEP_RESOLVED_FILE written: ~dependency_resolved.txt"
)
call :log "[INFO] DEP_RESOLVED_FROM=requirements.txt"
call :log "[INFO] DEP_INSTALL_SOURCE=requirements.txt"
call :log "[TRACE] dep install phase: start"
if exist "requirements.txt" (
  rem derived requirement, CLAUDE.md Item 42 precondition: the dependency-install phase is
  rem plausibly the single longest silent stretch in a fresh build, but previously had no
  rem [INFO]-tier progress line -- only [TRACE]/[INSTALL] lines, which a future console-tiering
  rem change, lever 1, could suppress by default, removing the only "something is happening"
  rem signal for this step. Mirrors the existing conda-create/PyInstaller-build precedents.
  call :log "[INFO] Installing dependencies -- this may take a few minutes..."
  if exist "~reqs_conda.txt" del "~reqs_conda.txt"
  call :log "[TRACE] heuristic augmentation: ~prep_requirements.py"
  if "%HP_ENV_MODE%"=="conda" (
    "%CONDA_BASE_PY%" "~prep_requirements.py" "requirements.txt" >nul 2>> "%LOG%"
  ) else (
    "%HP_PY%" "~prep_requirements.py" "requirements.txt" >nul 2>> "%LOG%"
  )
  call :log "[TRACE] heuristic augmentation: complete"
  if "%HP_ENV_MODE%"=="conda" (
    if not defined HP_DEP_SKIP (
      call :log "[INSTALL] conda bulk from ~reqs_conda.txt"
      call :conda_bulk_install
      if errorlevel 1 (
        call :log "[INSTALL] conda per-pkg fallback"
        for /f "usebackq delims=" %%P in ("~reqs_conda.txt") do (
          call "%CONDA_BAT%" install -y -n "%ENVNAME%" --override-channels -c conda-forge %%P >> "%LOG%" 2>&1
        )
      )
    )
    call :log "[INSTALL] pip gap fill from requirements.txt"
    "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1
    if errorlevel 1 (
      echo *** Warning: Some requirements may have failed to install.
      call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing."
    )
  ) else if "%HP_ENV_MODE%"=="venv" (
    "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1
    if errorlevel 1 (
      echo *** Warning: Some requirements may have failed to install.
      call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing."
    )
  ) else if "%HP_ENV_MODE%"=="uv" (
    rem derived requirement: uv pip install targets the uv venv explicitly via --python.
    rem HP_DEP_SKIP is set by dep_check before this block; 'if not defined' evaluates at
    rem runtime so there is no block-parse-time expansion issue.
    if not defined HP_DEP_SKIP (
      "%HP_UV_EXE%" pip install --python "%HP_PY%" -r requirements.txt >> "%LOG%" 2>&1
      if errorlevel 1 (
        echo *** Warning: Some requirements may have failed to install.
        call :log "[WARN] uv pip install -r requirements.txt failed; some packages may be missing."
        set "UV_FALLBACK_REASON=dep_install_failed"
        call :log "[WARN] UV_FALLBACK reason=dep_install_failed"
      ) else (
        rem REQ-005.11: install fully succeeded; PEP 723 write-back may run for this round.
        set "HP_UV_INSTALL_OK=1"
      )
    ) else (
      rem REQ-005.11: HP_DEP_SKIP means the lock already satisfied every package -- still a
      rem confirmed-installed state, not a failure to distinguish from a genuine install.
      set "HP_UV_INSTALL_OK=1"
    )
    call :log "[INFO] UV_USED=1"
  ) else if "%HP_ENV_MODE%"=="embed" (
    rem REQ-009 Tier 5: embed is a private, bootstrapper-owned interpreter like venv, not a
    rem shared/uncontrolled one like system -- installing into it is exactly the point, so it
    rem must NOT fall into the system catch-all below -- that branch deliberately skips install.
    "%HP_PY%" -m pip install -r requirements.txt >> "%LOG%" 2>&1
    if errorlevel 1 (
      echo *** Warning: Some requirements may have failed to install.
      call :log "[WARN] pip install -r requirements.txt failed; some packages may be missing."
    )
  ) else (
    call :log "[WARN] System fallback: skipping requirement installation."
  )
)
rem --- Capture installed package state via pip freeze ---
if exist "~dependency_installed.txt" del "~dependency_installed.txt" >nul 2>&1
if "%HP_ENV_MODE%"=="uv" (
  "%HP_UV_EXE%" pip freeze --python "%HP_PY%" > "~dependency_installed.txt" 2>nul
) else (
  "%HP_PY%" -m pip freeze > "~dependency_installed.txt" 2>nul
)
set "HP_DEP_INST_RC=%errorlevel%"
if "%HP_DEP_INST_RC%"=="0" call :log "[INFO] DEP_INSTALLED_CAPTURED=1"
if not "%HP_DEP_INST_RC%"=="0" (
  if exist "~dependency_installed.txt" del "~dependency_installed.txt" >nul 2>&1
  call :log "[WARN] DEP_INSTALLED_CAPTURE_FAILED=1"
)
set "HP_DEP_INST_RC="
rem --- Capture resolved environment snapshot ---
rem derived requirement: goto avoids %errorlevel% parse-time expansion that
rem would occur inside a parenthesized if-block -- cmd.exe expands %var% for
rem the whole block at parse time, so set HP_LOCK_RC=%errorlevel% inside an
rem if block always captures the pre-block errorlevel, not conda list's exit code.
if "%HP_ENV_MODE%"=="uv" (
  rem derived requirement: dep_check.py and selfapps_depcheck.ps1 expect
  rem ~environment.lock.txt regardless of env mode; reuse the pip freeze output
  rem already captured in ~dependency_installed.txt to avoid a second freeze call.
  if exist "~dependency_installed.txt" copy /y "~dependency_installed.txt" "~environment.lock.txt" >nul 2>&1
  if exist "~environment.lock.txt" call :log "[INFO] Environment snapshot written: ~environment.lock.txt"
  if not exist "~environment.lock.txt" (
    set "UV_FALLBACK_REASON=lock_failed"
    call :log "[WARN] UV_FALLBACK reason=lock_failed"
  )
  goto :lock_done
)
if not "%HP_ENV_MODE%"=="conda" goto :lock_done
call :log "[INFO] Capturing environment snapshot..."
call "%CONDA_BAT%" list -n "%ENVNAME%" --export > "~environment.lock.txt" 2>> "%LOG%"
set "HP_LOCK_RC=%errorlevel%"
if "%HP_LOCK_RC%"=="0" (
  for %%Z in ("~environment.lock.txt") do if %%~zZ GTR 0 (
    call :log "[INFO] Environment snapshot written: ~environment.lock.txt"
    goto :lock_done
  )
  call :log "[WARN] Environment snapshot: conda list succeeded but output is empty."
  if exist "~environment.lock.txt" del "~environment.lock.txt" >nul 2>&1
) else (
  call :log "[WARN] Environment snapshot failed (conda list rc=%HP_LOCK_RC%)."
  if exist "~environment.lock.txt" del "~environment.lock.txt" >nul 2>&1
)
:lock_done
rem REQ-005.11: fresh-install PEP 723 write-back trigger. Must run after the lock
rem snapshot above (HP_UV_INSTALL_OK is set during dependency install, further up
rem this same :after_env_mode_selection scope) and before pyvisa detection below.
call :pep723_writeback fresh
rem Detect pyvisa/visa usage so harness sees NI-VISA requirements

call :emit_from_base64 "~detect_visa.py" HP_DETECT_VISA
if errorlevel 1 call :die "[ERROR] Could not write ~detect_visa.py"
set "NEED_VISA=0"
if exist "~visa.flag" del "~visa.flag"
"%HP_PY%" "~detect_visa.py" > "~visa.flag" 2>> "%LOG%"
for /f "usebackq delims=" %%V in ("~visa.flag") do set "NEED_VISA=%%V"
if "%NEED_VISA%"=="1" (
  call :log "[INFO] Detected pyvisa/visa import; NI-VISA install may be required."
) else (
  call :log "[INFO] No pyvisa/visa imports detected."
)
if exist "~visa.flag" del "~visa.flag"

rem --- NI-VISA presence check and install (REQ-008) ---
rem derived requirement: NEED_VISA must be exactly "1"; any other value (0, empty, error)
rem skips install to avoid hanging CI on non-visa projects.
if not "%NEED_VISA%"=="1" (
  call :log "[VISA] skipped (not_required)"
  goto visa_done
)
rem REQ-008: allow disabling the NI-VISA install for debugging, even when pyvisa/visa is detected.
if "%HP_SKIP_NIVISA%"=="1" (
  call :log "[VISA] skipped (disabled)"
  goto visa_done
)
reg query "HKLM\SOFTWARE\National Instruments\NI-VISA" /v "CurrentVersion" >nul 2>&1
if not errorlevel 1 (
  call :log "[VISA] present"
  goto visa_done
)
set "NIVISA_INSTALLER=~ni-visa-runtime.exe"
set "HP_VISA_DLVIA=curl"
rem derived requirement: --retry/--max-time match every other curl download in this file
rem (uv/Miniconda/get-pip/embed) -- this call was the one exception, so a stalled connection
rem (e.g. a captive-portal network that accepts the TCP handshake but never completes the HTTP
rem response) could hang the whole bootstrap indefinitely. See "no-timeout audit" in
rem docs/agent-lessons-learned.md.
curl -L --silent --fail --retry 3 --retry-delay 5 --max-time 120 -o "%NIVISA_INSTALLER%" "https://download.ni.com/support/nipkg/products/ni-v/ni-visa/21.5/online/ni-visa_21.5_online.exe" 2>> "%LOG%"
if errorlevel 1 (
  set "HP_VISA_DLVIA=powershell"
  rem derived requirement: curl can leave a partial file on failure; delete before fallback
  rem so PowerShell does not find a corrupt file and skip its own download attempt.
  if exist "%NIVISA_INSTALLER%" del "%NIVISA_INSTALLER%" >nul 2>&1
  powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri 'https://download.ni.com/support/nipkg/products/ni-v/ni-visa/24.0/runtime/ni-visa-runtime_24.0.0_windows.exe' -OutFile '%NIVISA_INSTALLER%' -UseBasicParsing -TimeoutSec 120 -ErrorAction Stop } catch { exit 1 }" 2>> "%LOG%"
)
if not exist "%NIVISA_INSTALLER%" (
  call :log "[VISA] install_failed (download)"
  goto visa_done
)
rem REQ-008 diagnostic: record the downloaded installer's provenance, size, and PE validity so a
rem non-zero installer exit code can be classified (blocked/redirected payload vs a real installer
rem that refused unattended install). These are read-only probes -- they never touch the install.
call :log "[VISA] download method: %HP_VISA_DLVIA%"
set "HP_VISA_DLSIZE=0"
for %%S in ("%NIVISA_INSTALLER%") do set "HP_VISA_DLSIZE=%%~zS"
call :log "[VISA] installer file size: %HP_VISA_DLSIZE% bytes"
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { $fs=[System.IO.File]::OpenRead('%NIVISA_INSTALLER%'); $a=$fs.ReadByte(); $b=$fs.ReadByte(); $fs.Close(); if ($a -eq 77 -and $b -eq 90) { 'PE_OK' } else { 'NOT_PE' } } catch { 'PROBE_ERR' }" > "~visa_pe.txt" 2>nul
set "HP_VISA_PE="
if exist "~visa_pe.txt" for /f "usebackq delims=" %%P in ("~visa_pe.txt") do set "HP_VISA_PE=%%P"
if exist "~visa_pe.txt" del "~visa_pe.txt" >nul 2>&1
call :log "[VISA] installer PE check: %HP_VISA_PE%"
rem derived requirement: [Active Backlog item 14] bound the installer launch with a generous
rem timeout ceiling instead of an unbounded wait. Real-world NI installer-family reports (NI
rem Community forum threads on the shared NI Package Manager stack, which NI-VISA's own
rem installer is built on) document multi-hour installs when antivirus scans every file the
rem installer writes; this repo's own CI evidence separately shows NI-VISA failing FAST
rem (~10s, installer_rc=-125083) in this environment -- see CLAUDE.md's "NI-VISA real install
rem fails fast in CI" Known Finding. 90 minutes is a generous ceiling against a genuinely hung
rem install (the failure mode a timeout exists to catch) without being the multi-hour extreme
rem outlier from the (heavier, different-product) NI Package Manager reports.
call :run_installer_timeout "%NIVISA_INSTALLER%" "--quiet --accept-eulas --prevent-reboot --prevent-activation" 5400000 "NI-VISA"
set "HP_VISA_INSTALLER_RC=%ERRORLEVEL%"
rem derived requirement: capture the installer exit code so diagnostics can tell a hard failure
rem (non-zero rc) apart from a slow-but-progressing install (rc=0, registry not yet populated).
call :log "[VISA] installer exit code: %HP_VISA_INSTALLER_RC%"
rem derived requirement: NI installers may spawn child processes; poll the registry before declaring
rem post-install failure. Budget is configurable via HP_NIVISA_WAIT_SECS so a dedicated diagnostic
rem lane can wait out a slow (~30-45 min) NI-VISA install without slowing gating lanes. Default keeps
rem the original ~15s behavior (3 retries x ~5s); single-line ifs avoid parenthesized parse-time issues.
set "HP_VISA_PINGN=6"
set "HP_VISA_MAXRETRY=3"
if defined HP_NIVISA_WAIT_SECS set "HP_VISA_PINGN=31"
if defined HP_NIVISA_WAIT_SECS set /a HP_VISA_MAXRETRY=%HP_NIVISA_WAIT_SECS%/30
if %HP_VISA_MAXRETRY% lss 3 set "HP_VISA_MAXRETRY=3"
set "HP_VISA_RETRY=0"
:visa_post_check
reg query "HKLM\SOFTWARE\National Instruments\NI-VISA" /v "CurrentVersion" >nul 2>&1
if not errorlevel 1 (
  call :log "[VISA] install_success"
  goto visa_cleanup
)
set /a HP_VISA_RETRY+=1
if %HP_VISA_RETRY% lss %HP_VISA_MAXRETRY% (
  call :log "[VISA] post-check waiting; retry %HP_VISA_RETRY%/%HP_VISA_MAXRETRY% (installer_rc=%HP_VISA_INSTALLER_RC%)"
  ping -n %HP_VISA_PINGN% 127.0.0.1 >nul 2>&1
  goto visa_post_check
)
call :log "[VISA] install_failed (post_check_timeout) installer_rc=%HP_VISA_INSTALLER_RC%"
:visa_cleanup
if exist "%NIVISA_INSTALLER%" del "%NIVISA_INSTALLER%" >nul 2>&1
:visa_done

rem --- Write env state for fast path on next run ---
rem derived requirement: goto avoids %errorlevel% parse-time expansion inside
rem parenthesized if-blocks; same pattern as dep-check and lock-capture blocks.
call :emit_from_base64 "~env_state.py" HP_ENV_STATE
if errorlevel 1 goto :env_state_write_done
"%HP_PY%" "~env_state.py" --write >> "%LOG%" 2>&1
set "HP_ENV_STATE_WRITE_RC=%errorlevel%"
if exist "~env_state.py" del "~env_state.py" >nul 2>&1
if "%HP_ENV_STATE_WRITE_RC%"=="0" call :log "[INFO] Env state written: ~env.state.json"
if not "%HP_ENV_STATE_WRITE_RC%"=="0" call :log "[WARN] Env state write failed (rc=%HP_ENV_STATE_WRITE_RC%)."
set "HP_ENV_STATE_WRITE_RC="
:env_state_write_done
goto :after_env_bootstrap

:ci_skip_entry
call :log "[INFO] CI self-test: skipping environment bootstrap"
set "HP_ENTRY="
set "HP_CRUMB="
if exist "~entry.abs" del "~entry.abs"
rem --- stage helper ---
call :emit_from_base64 "~find_entry.py" HP_FIND_ENTRY
if errorlevel 1 (
  call :die "[ERROR] CI skip: entry helper staging failed"
  goto :after_env_bootstrap
)
call :update_find_entry_abs
call :verify_find_entry_helper
if errorlevel 1 call :die "[ERROR] find_entry helper syntax error"

rem --- locate a Python ---
set "HP_SYS_PY=" & set "HP_SYS_PY_ARGS="
set "HP_SYS_PY_LOGGED="
where python >nul 2>&1 && set "HP_SYS_PY=python"
if not defined HP_SYS_PY (
  where py >nul 2>&1 && (set "HP_SYS_PY=py" & set "HP_SYS_PY_ARGS=-3")
)

if defined HP_SYS_PY for %%C in ("%HP_SYS_PY%") do set "HP_SYS_PY=%%~C"
if defined HP_SYS_PY_ARGS for %%A in ("%HP_SYS_PY_ARGS%") do set "HP_SYS_PY_ARGS=%%~A"

rem --- run helper and capture RELATIVE crumb ---
set "HP_CRUMB="
if not defined HP_SYS_PY goto :ci_skip_helper_done
rem derived requirement: use goto instead of if-block; DisableDelayedExpansion coalesces
rem the entire block at parse time, so %HP_CRUMB_FILE% (set mid-block) was empty when
rem used as a redirect target, causing "syntax of the command is incorrect" / exit 255.
rem derived requirement: CI observed `'python" "~find_entry.py' is not recognized` when
rem helper args were empty. Keep the helper invocation split so CMD never appends a stray
rem quote to the interpreter token, and route stdout through a file to avoid shell quoting drift.
if not defined HP_SYS_PY_LOGGED (
  if defined HP_SYS_PY_ARGS (
    >> "%LOG%" echo Helper command: "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_FIND_ENTRY_ABS%"
  ) else (
    >> "%LOG%" echo Helper command: "%HP_SYS_PY%" "%HP_FIND_ENTRY_ABS%"
  )
  set "HP_SYS_PY_LOGGED=1"
)
rem derived requirement: CI skip jobs sometimes run from a borrowed working directory.
rem Normalize the helper root so helper discovery always executes from the bootstrapper tree.
set "HP_HELPER_ROOT=%HP_SCRIPT_ROOT%"
for %%R in ("%HP_HELPER_ROOT%") do set "HP_HELPER_ROOT=%%~fR"
if not defined HP_HELPER_ROOT set "HP_HELPER_ROOT=%CD%"
if not exist "%HP_HELPER_ROOT%" mkdir "%HP_HELPER_ROOT%" >nul 2>&1
set "HP_CHOOSER_ROOT=%HP_HELPER_ROOT%"
for %%R in ("%HP_HELPER_ROOT%tests") do if exist "%%~fR" set "HP_CHOOSER_ROOT=%%~fR"
rem derived requirement: the helper enumerates cwd *.py files; pivot into tests/ when present
rem so crumbs reference the self-test entry scripts without emitting "The system cannot find the path specified.".
for %%R in ("%HP_CHOOSER_ROOT%") do set "HP_CHOOSER_ROOT=%%~fR"
if not exist "%HP_CHOOSER_ROOT%" set "HP_CHOOSER_ROOT=%HP_HELPER_ROOT%"
set "HP_CRUMB_FILE=%HP_CHOOSER_ROOT%"
if not "%HP_CRUMB_FILE:~-1%"=="\" set "HP_CRUMB_FILE=%HP_CRUMB_FILE%\"
set "HP_CRUMB_FILE=%HP_CRUMB_FILE%~crumb.txt"
if exist "%HP_CRUMB_FILE%" del "%HP_CRUMB_FILE%" >nul 2>&1
set "HP_CHOOSER_PUSHD="
if exist "%HP_CHOOSER_ROOT%" (
  pushd "%HP_CHOOSER_ROOT%" >nul 2>&1
  set "HP_CHOOSER_PUSHD=1"
) else (
  echo [WARN] pushd skipped, chooser root missing: %HP_CHOOSER_ROOT%
)
if defined HP_SYS_PY_ARGS (
  "%HP_SYS_PY%" %HP_SYS_PY_ARGS% -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul
) else (
  "%HP_SYS_PY%" -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul
)
if defined HP_SYS_PY_ARGS (
  "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_FIND_ENTRY_ABS%" > "%HP_CRUMB_FILE%" 2>> "%LOG%"
) else (
  "%HP_SYS_PY%" "%HP_FIND_ENTRY_ABS%" > "%HP_CRUMB_FILE%" 2>> "%LOG%"
)
if defined HP_CHOOSER_PUSHD (
  popd >nul 2>&1
  set "HP_CHOOSER_PUSHD="
)
if exist "%HP_CRUMB_FILE%" (
  for /f "usebackq delims=" %%L in ("%HP_CRUMB_FILE%") do if not defined HP_CRUMB set "HP_CRUMB=%%L"
  del "%HP_CRUMB_FILE%" >nul 2>&1
)
if /i not "%HP_CHOOSER_ROOT%"=="%HP_HELPER_ROOT%" (
  if defined HP_CRUMB set "HP_CRUMB=tests\%HP_CRUMB%"
)
:ci_skip_helper_done

if not defined HP_CRUMB (
  echo [INFO] CI skip: no entry script detected.
  goto :after_env_bootstrap
)

rem --- write breadcrumb EXACTLY (no trailing punctuation) ---
echo Chosen entry: %HP_CRUMB%
>> "%LOG%" echo Chosen entry: %HP_CRUMB%

rem optional: set HP_ENTRY if helper emitted an abs path file
if exist "~entry.abs" set /p HP_ENTRY=<"~entry.abs"
rem Helper now only prints the crumb; reuse it for optional smoke runs.
if not defined HP_ENTRY if defined HP_CRUMB set "HP_ENTRY=%HP_CRUMB%"

rem optional best-effort run; never install in skip mode
if defined HP_ENTRY if defined HP_SYS_PY (
  if defined HP_SYS_PY_ARGS (
    "%HP_SYS_PY%" %HP_SYS_PY_ARGS% "%HP_ENTRY%" > "~run.out.txt" 2>&1 || echo [WARN] CI skip: system Python non-zero
  ) else (
    "%HP_SYS_PY%" "%HP_ENTRY%" > "~run.out.txt" 2>&1 || echo [WARN] CI skip: system Python non-zero
  )
)
call :append_env_mode_row
goto :after_env_bootstrap

:after_env_bootstrap
if defined HP_CI_SKIP_ENV goto :after_env_skip
call :determine_entry "%~1"
if errorlevel 11 (
  call :write_status "error" 1 %PYCOUNT%
  exit /b 1
)
if errorlevel 1 call :die "[ERROR] Could not determine entry point"
if "%HP_ENTRY%"=="" (
  call :log "[INFO] No entry script detected; skipping PyInstaller packaging."
) else (
  call :run_entry_smoke
)

rem REQ-009/REQ-005.10 slice 3: if warnfix left dependencies unresolved under the current
rem provider AND the user approved (HP_CASCADE_APPROVED, set in :warnfix_cascade_detect),
rem re-attempt the dependency phase under the next REQ-009 provider tier. The per-tier
rem HP_CASCADE_TRIED_* guards inside :provider_cascade ensure a tier is never used as a
rem cascade source twice, so an unresolvable dependency exhausts the tiers and stops --
rem it never loops. Each re-attempt re-enters at :try_conda_create / :after_env_mode_selection.
if defined HP_CASCADE_APPROVED goto :provider_cascade
:after_cascade_decision
rem derived requirement: restore HP_PY if a cascade attempt clobbered it. A DECLINED/failed
rem fallback tier (:try_venv_fallback / :try_system_fallback) clears its own HP_PY on failure
rem exit as ITS OWN invariant (must leave no trace of a speculative attempt -- see
rem docs/agent-lessons-learned.md "A declined/failed fallback tier must clear HP_PY, not just
rem return failure"), which is correct for the INITIAL fallback chain but destructive here: on
rem cascade re-entry, HP_PY may already hold a working interpreter from an earlier successful
rem build (the one :print_postflight_briefing is about to describe as "your standalone
rem application is ready"). Every path that reaches this label via :provider_cascade's own
rem exhaustion/decline branches (not the direct fall-through when no cascade was ever approved)
rem is exactly "keeping current build" -- restore what :provider_cascade saved on entry.
if defined HP_CASCADE_SAVED_PY set "HP_PY=%HP_CASCADE_SAVED_PY%"
set "HP_CASCADE_SAVED_PY="
if /i "%HP_BOOTSTRAP_STATE%"=="ok" (
  call :write_status ok 0 %PYCOUNT%
) else (
  call :write_status "%HP_BOOTSTRAP_STATE%" 0 %PYCOUNT%
)
goto :success

:after_env_skip
call :write_status ok 0 %PYCOUNT%
goto :success

:success
rem derived requirement: skip write when DEP_SOURCE=unknown (EXE fast path / no-python-files paths
rem fire before dep resolution; preserve any existing dependency_source.txt from previous run).
if not "%DEP_SOURCE%"=="unknown" (
  echo dependency_source=%DEP_SOURCE%> "dependency_source.txt"
  echo *** [INFO] Dependency source logged to dependency_source.txt
)
rem REQ-016: show post-flight briefing when a full EXE build completed. HP_BUILD_OK is only
rem set once :run_entry_smoke actually attempts a build (undefined if no entry file was ever
rem found), so "HP_BUILD_OK defined AND no EXE exists" precisely means packaging was attempted
rem and failed outright (both PyInstaller and the Nuitka fallback) -- distinct from "no entry
rem file" or a declined system-Python build, where no briefing of either kind applies.
if not defined HP_FASTPATH_USED (
  if exist "dist\%ENVNAME%.exe" (
    call :print_postflight_briefing
  ) else if defined HP_BUILD_OK (
    call :print_no_exe_briefing
  )
) else if defined HP_FASTPATH_RUN_FAILED (
  rem [REQ-027] P2 honest messaging: the fast path stays zero-friction for its own PROMPTS --
  rem no consent gate added here -- but a print-only informational note costs nothing and
  rem closes a genuine gap -- before this, a cached EXE kept despite a later nonzero exit,
  rem classified alive/healthy at the fail-fast probe so never discarded/rebuilt, see
  rem :try_fast_exe, had NO postflight signal at all beyond one WARN log line buried among
  rem other console output.
  call :print_fastpath_ambiguous_note
)
rem CLAUDE.md Active Backlog Item 39: gated on HP_FRESH_BUILD_OK, not merely "HP_FASTPATH_USED
rem unset" (CodeRabbit review, PR #460, caught a real bug in the original gate: a skipped or
rem failed rebuild also leaves HP_FASTPATH_USED unset, but a stale dist\%ENVNAME%.exe left over
rem from an earlier successful run would still be sitting there -- writing the CURRENT sources'
rem hash against that OLD binary would make the next run wrongly trust it as "fresh"). Set ONLY
rem in :run_entry_smoke's own genuine build-success branches -- see the reset/set sites there.
rem HP_FRESH_BUILD_OK being set already implies the fast path was not taken (a build can only be
rem attempted after the fast path declined to fire), so this alone is a strictly more precise
rem replacement for the old HP_FASTPATH_USED check, not an additional condition alongside it.
if defined HP_FRESH_BUILD_OK call :write_fast_hash
call :release_lock
rem REQ-016: retain terminal window on success so user can read the output.
if not defined HP_CI_LANE (
  pause
)
exit /b 0

:provider_cascade
rem REQ-009/REQ-005.10 slice 3: re-attempt the dependency phase under the next provider tier.
rem Dispatch is goto-based (no parenthesized interdependent sets) to avoid CMD parse-time
rem expansion traps. Each tier is marked HP_CASCADE_TRIED_<tier> the first time it is used as a
rem cascade source; a tier is never used twice, so tiers exhaust and the run stops (no loop).
rem HP_ENV_MODE only advances (uv -> conda -> embed -> venv -> system), so re-entry cannot
rem revisit a tier. conda and embed both front-load acquisition of a FRESH/pinned interpreter
rem before falling back to venv/system, which merely wrap whatever Python is already ambient on
rem the machine -- see docs/agent-interconnect.md "Standalone Python-download tier" for the
rem ordering rationale. NOTE: the :log messages below say "uv to conda" (not "uv -> conda") on
rem purpose -- :log echoes UNQUOTED, so a ">" in the message would be parsed as redirection and
rem eat the line (see docs/agent-lessons-learned.md). Do not "fix" these to arrows.
rem derived requirement: save HP_PY before any cascade tier attempt can clobber it (a failed/
rem declined tier clears its own HP_PY on the way out); :after_cascade_decision restores this
rem on every exhaustion/decline exit from this label -- see the comment there for why.
set "HP_CASCADE_SAVED_PY=%HP_PY%"
set "HP_CASCADE_APPROVED="
if /i "%HP_ENV_MODE%"=="uv" goto :cascade_from_uv
if /i "%HP_ENV_MODE%"=="conda" goto :cascade_from_conda
if /i "%HP_ENV_MODE%"=="embed" goto :cascade_from_embed
if /i "%HP_ENV_MODE%"=="venv" goto :cascade_from_venv
rem NOTE: no "system" case here, by design -- system is the absolute last resort (Tier 4 by
rem naming/history, REQ-014-gated), no cascade target beyond it. A re-entry with
rem HP_ENV_MODE=system falls straight through to the catch-all below, exactly as a re-entry
rem with HP_ENV_MODE=embed used to before this reorder (when embed was terminal).
call :log "[INFO] REQ-009: provider tiers exhausted after %HP_ENV_MODE%; keeping current build."
goto :after_cascade_decision

:cascade_from_uv
if defined HP_CASCADE_TRIED_UV goto :after_cascade_decision
set "HP_CASCADE_TRIED_UV=1"
call :log "[INFO] REQ-009: cascading provider uv to conda; re-attempting dependencies."
echo *** [INFO] Trying the next Python provider (conda) to resolve dependencies...
call :cascade_acquire_conda
if not defined CONDA_BAT goto :cascade_conda_unavailable
set "HP_UV_PROVIDING_PYTHON="
set "HP_ENV_MODE=conda"
set "ENV_PATH=%MINICONDA_ROOT%\envs\%ENVNAME%"
goto :try_conda_create
:cascade_conda_unavailable
call :log "[WARN] REQ-009: cascade to conda unavailable (Miniconda not installed); keeping current build."
goto :after_cascade_decision
:cascade_conda_create_failed
rem derived requirement: reached from :conda_create_failed / :conda_create_done (via goto, not
rem call) when a genuine conda-create failure occurs during THIS cascade re-entry specifically --
rem see the comment at :conda_create_failed for the full rationale. Deliberately does NOT call
rem :die: :after_cascade_decision's own "keeping current build" restore only works correctly when
rem HP_BOOTSTRAP_STATE is left as whatever it already was (the prior successful build's "ok"),
rem not overwritten to "error" the way :die would.
call :log "[WARN] REQ-009: cascade target conda create failed; keeping current build."
goto :after_cascade_decision

:cascade_from_conda
if defined HP_CASCADE_TRIED_CONDA goto :after_cascade_decision
set "HP_CASCADE_TRIED_CONDA=1"
if "%HP_FORCE_CONDA_ONLY%"=="1" goto :cascade_condaonly_stop
call :log "[INFO] REQ-009: cascading provider conda to embed; re-attempting dependencies."
echo *** [INFO] Trying the next Python provider (embed) to resolve dependencies...
call :try_embed_fallback
if errorlevel 1 goto :cascade_embed_unavailable
goto :after_env_mode_selection
:cascade_condaonly_stop
call :log "[INFO] REQ-009: conda-only mode; cascade beyond conda suppressed; keeping current build."
goto :after_cascade_decision
:cascade_embed_unavailable
call :log "[WARN] REQ-009: cascade target embedded Python unavailable; keeping current build."
goto :after_cascade_decision

:cascade_from_embed
if defined HP_CASCADE_TRIED_EMBED goto :after_cascade_decision
set "HP_CASCADE_TRIED_EMBED=1"
call :log "[INFO] REQ-009: cascading provider embed to venv; re-attempting dependencies."
echo *** [INFO] Trying the next Python provider (venv) to resolve dependencies...
call :try_venv_fallback
if errorlevel 1 goto :cascade_venv_unavailable
goto :after_env_mode_selection
:cascade_venv_unavailable
call :log "[WARN] REQ-009: cascade target venv unavailable; keeping current build."
goto :after_cascade_decision

:cascade_from_venv
if defined HP_CASCADE_TRIED_VENV goto :after_cascade_decision
set "HP_CASCADE_TRIED_VENV=1"
rem REQ-009/REQ-014: system Python is Tier 4 (by naming/history; executed last in chain order);
rem reached in any run and gated only by the REQ-014 consent prompt inside :try_system_fallback
rem (no env flag). A decline keeps the current build.
call :log "[INFO] REQ-009: cascading provider venv to system; re-attempting dependencies."
call :try_system_fallback
if errorlevel 1 goto :cascade_system_unavailable
goto :after_env_mode_selection
:cascade_system_unavailable
call :log "[WARN] REQ-009: cascade target system Python unavailable; keeping current build."
goto :after_cascade_decision

:cascade_acquire_conda
rem REQ-009 slice 3: a uv-first run skipped Miniconda; acquire it on demand for a uv->conda
rem cascade. MINICONDA_ROOT / CONDA_MAIN / CONDA_ALT are already set (near line 410) even in
rem uv-first runs, so :select_conda_bat and :try_conda_install work without further setup.
call :select_conda_bat
if defined CONDA_BAT goto :eof
echo [INFO] Installing Miniconda into "%MINICONDA_ROOT%"...
set "HP_CONDA_JUST_INSTALLED=1"
call :download_miniconda_exe
if exist "%TEMP%\miniconda.exe" call :try_conda_install
if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1
call :select_conda_bat
goto :eof

:count_python
set "NAME=%~1"
if "%NAME%"=="" exit /b 0
if "%NAME:~0,1%"=="~" exit /b 0
set /a PYCOUNT+=1 >nul
exit /b 0

:check_hidden_ext_hint
rem CLAUDE.md Item 32: Windows hides known file extensions by default, so a beginner
rem who saves script.py from a text editor (or downloads it as an email attachment)
rem can end up with script.py.txt without realizing it. Purely additive diagnostic
rem hint -- never changes the exit code or any other behavior. Deliberately does not
rem echo the matched filename: a legal Windows filename can contain "&", a live
rem cmd.exe operator once substituted into an unquoted echo/:log line (see
rem docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry) -- a plain
rem existence check via errorlevel sidesteps that hazard entirely.
dir /b /a-d *.py.txt >nul 2>&1
if errorlevel 1 exit /b 0
echo Hint: found a file ending in .py.txt -- Windows may be hiding known file extensions.
>> "%LOG%" echo Hint: found a file ending in .py.txt -- Windows may be hiding known file extensions.
echo In File Explorer's View tab, turn on File name extensions, then rename the file so it ends in .py instead of .py.txt.
>> "%LOG%" echo In File Explorer's View tab, turn on File name extensions, then rename the file so it ends in .py instead of .py.txt.
call :log "[INFO] REQ-002: found a *.py.txt file -- Windows may be hiding known file extensions."
exit /b 0

:check_subfolder_hint
rem CLAUDE.md Item 43: entry detection and the top-level Python-file count are both
rem depth-1-only by design (matches the documented "drop run_setup.bat alongside your
rem .py files" contract), but a user who unzips a src/-style project layout and drops
rem run_setup.bat at the root sees "No Python files detected," which reads as false
rem when .py files are visibly one folder down. Purely additive diagnostic hint --
rem never changes the exit code or any other behavior. Genuinely depth-1-only: a
rem `for /d` loop over immediate subdirectories, NOT `dir /s` (unbounded-depth, would
rem falsely match a .py file buried many folders down). Skips bootstrapper-owned
rem folders (dist, build, and any ~/.-prefixed dir such as .uv_env, .git, ~uv_bin,
rem ~embed_python) so a leftover build/venv artifact from an earlier run can never
rem produce a false positive. Deliberately does not echo the matched subfolder name,
rem for the same "&" cmd.exe operator hazard :check_hidden_ext_hint's own header
rem comment documents. `/a-d` on the inner scan excludes directories -- a plain
rem `dir /b "X\*.py"` also matches a SUBDIRECTORY literally named *.py (e.g. a
rem package folder named "helpers.py"), the same distinction :count_python's own
rem top-level scan already makes via its own `dir /b /a-d *.py`. Per-candidate
rem exclusion logic is a `call`ed subroutine with single-condition `if` statements,
rem NOT a chained `if X if Y if Z (...)` line -- a real CI failure confirmed the
rem hint firing when it should not have (dist/build/tilde/dot exclusions in one
rem 4-deep chained-if line inside the for-loop body), and this repo's own
rem established pattern is call/goto-based dispatch over deep if-chaining for
rem exactly this reliability reason (see docs/agent-lessons-learned.md's
rem "Provider-cascade dispatch is goto-based on purpose"). The subdirectory name
rem is staged into HP_SFC via a plain `set` BEFORE the `call`, never as a `call`
rem argument -- `call` re-scans its OWN line a second time before dispatching
rem (see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry, "call
rem triggers cmd.exe's own second expansion pass"), so a subfolder literally
rem named e.g. "has%PATH%in-it" passed as a `call` argument would have that
rem text re-expanded into the REAL PATH variable's value during dispatch,
rem corrupting the scan target -- the same hazard class already found and
rem fixed once before for the conda native-DLL bundling loop (CodeRabbit
rem review, CLAUDE.md Item 24).
for /d %%D in (*) do (
  set "HP_SFC=%%D"
  call :subfolder_hint_check_one
  if not errorlevel 1 goto :subfolder_hint_found
)
exit /b 0
:subfolder_hint_check_one
if /i "%HP_SFC%"=="dist" exit /b 1
if /i "%HP_SFC%"=="build" exit /b 1
if "%HP_SFC:~0,1%"=="~" exit /b 1
if "%HP_SFC:~0,1%"=="." exit /b 1
dir /b /a-d "%HP_SFC%\*.py" >nul 2>&1
exit /b %errorlevel%
:subfolder_hint_found
echo Hint: found .py file(s) in a subfolder, but this script only looks in the exact folder it is in.
>> "%LOG%" echo Hint: found .py file(s) in a subfolder, but this script only looks in the exact folder it is in.
echo Move run_setup.bat next to your scripts, or move your scripts up into this folder, then run it again.
>> "%LOG%" echo Move run_setup.bat next to your scripts, or move your scripts up into this folder, then run it again.
call :log "[INFO] REQ-002: found .py file(s) in an immediate subfolder; this script only scans its own folder."
exit /b 0

:select_conda_bat
set "CONDA_BAT="
if exist "%CONDA_MAIN%" set "CONDA_BAT=%CONDA_MAIN%"
if not defined CONDA_BAT if exist "%CONDA_ALT%" set "CONDA_BAT=%CONDA_ALT%"
if defined CONDA_BAT if not exist "%CONDA_BAT%" set "CONDA_BAT="
exit /b 0

:download_miniconda_exe
set "HP_MINICONDA_ACTIVE_URL=%HP_MINICONDA_URL%"
if "%HP_TEST_CONDA_DL_FALLBACK%"=="1" if not defined HP_CONDA_DL_INJECTED set "HP_MINICONDA_ACTIVE_URL=https://miniconda-test-fail.invalid/Miniconda3-latest-Windows-x86_64.exe"
if "%HP_OFFLINE_MODE%"=="1" (
  call :log "[INFO] REQ-013: Offline mode: skipping Miniconda download."
  goto :eof
)
call :log "[INFO] Downloading Miniconda from %HP_MINICONDA_ACTIVE_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_ACTIVE_URL%" -o "%TEMP%\miniconda.exe" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\miniconda.exe" goto :eof
echo *** curl download failed, trying PowerShell...
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_MINICONDA_ACTIVE_URL%' -OutFile '%TEMP%\miniconda.exe' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\miniconda.exe" goto :eof
if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1
rem REQ-013: primary download failed; check connectivity before trying fallback.
rem Skip connectivity check when HP_TEST_CONDA_DL_FALLBACK=1 (failure was intentional).
if not "%HP_TEST_CONDA_DL_FALLBACK%"=="1" call :check_net_after_dl_fail
if "%HP_OFFLINE_MODE%"=="1" goto :eof
if defined HP_CONDA_DL_INJECTED (
  call :log "[ERROR] Injected HP_MINICONDA_URL failed; not trying fallback."
  goto :eof
)
call :log "[INFO] Trying fallback Miniconda URL: %HP_MINICONDA_FALLBACK_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_FALLBACK_URL%" -o "%TEMP%\miniconda.exe" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\miniconda.exe" (
  call :log "[INFO] Miniconda download succeeded from fallback URL."
  goto :eof
)
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_MINICONDA_FALLBACK_URL%' -OutFile '%TEMP%\miniconda.exe' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\miniconda.exe" (
  call :log "[INFO] Miniconda download succeeded from fallback URL."
  goto :eof
)
call :log "[WARN] Miniconda: all download URLs failed."
goto :eof

:download_get_pip
rem REQ-023b: fetches get-pip.py so the venv fallback tier can bootstrap pip after a
rem --without-pip venv creation (see :try_venv_fallback). Sets HP_GETPIP_PY to the downloaded
rem file path on success; leaves it undefined/absent on failure so the caller can detect it via
rem "if not exist". No interactive connectivity gate here (unlike :download_miniconda_exe's use
rem of :check_net_after_dl_fail) -- this runs deep inside a fallback tier where the zero-friction
rem design intent reserves the one interactive prompt for the REQ-014 system-Python consent gate;
rem a plain download failure here should silently decline the tier, not stop to ask the user.
set "HP_GETPIP_PY="
rem The HP_TEST_FORCE_VENV_CREATE_FAIL / HP_TEST_FORCE_EMBED_REAL exceptions below let CI
rem exercise this real download path while still using HP_OFFLINE_MODE=1 to cheaply skip
rem unrelated downloads (Miniconda, the embed zip's own earlier PowerShell-stage download)
rem elsewhere in the same test run -- neither flag weakens real-user offline protection, since
rem they are never set outside CI coverage.
set "HP_GETPIP_SKIP_OFFLINE=1"
if "%HP_TEST_FORCE_VENV_CREATE_FAIL%"=="1" set "HP_GETPIP_SKIP_OFFLINE="
if "%HP_TEST_FORCE_EMBED_REAL%"=="1" set "HP_GETPIP_SKIP_OFFLINE="
if "%HP_OFFLINE_MODE%"=="1" if defined HP_GETPIP_SKIP_OFFLINE (
  call :log "[INFO] REQ-013: Offline mode: skipping get-pip.py download."
  goto :eof
)
call :log "[INFO] Downloading get-pip.py from %HP_GETPIP_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_GETPIP_URL%" -o "%TEMP%\get-pip.py" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\get-pip.py" (
  set "HP_GETPIP_PY=%TEMP%\get-pip.py"
  goto :eof
)
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_GETPIP_URL%' -OutFile '%TEMP%\get-pip.py' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\get-pip.py" (
  set "HP_GETPIP_PY=%TEMP%\get-pip.py"
  goto :eof
)
if exist "%TEMP%\get-pip.py" del "%TEMP%\get-pip.py" >nul 2>&1
call :log "[INFO] Trying fallback get-pip.py URL: %HP_GETPIP_FALLBACK_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_GETPIP_FALLBACK_URL%" -o "%TEMP%\get-pip.py" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\get-pip.py" (
  call :log "[INFO] get-pip.py download succeeded from fallback URL."
  set "HP_GETPIP_PY=%TEMP%\get-pip.py"
  goto :eof
)
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_GETPIP_FALLBACK_URL%' -OutFile '%TEMP%\get-pip.py' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%TEMP%\get-pip.py" (
  call :log "[INFO] get-pip.py download succeeded from fallback URL."
  set "HP_GETPIP_PY=%TEMP%\get-pip.py"
  goto :eof
)
call :log "[WARN] get-pip.py: all download URLs failed."
goto :eof

:handle_conda_failure
set "HP_FAIL_MSG=%~1"
if not "%HP_FAIL_MSG%"=="" call :log "%HP_FAIL_MSG%"

if "%HP_FORCE_CONDA_ONLY%"=="1" (
  rem derived requirement: the dedicated conda CI slice must surface conda failures instead of hiding behind venv/system fallbacks.
  call :log "[INFO] Conda-only mode: skipping fallback attempts."
  exit /b 0
)

rem REQ-009 (reordered): embedded Python (Tier 5 by naming/history, executed 3rd since this
rem reorder) is attempted immediately after conda fails, ahead of venv/system -- so a user who
rem pinned a newer-than-ambient Python version via runtime.txt/pyproject.toml still gets it via
rem a fresh checksummed python.org download, before falling back to a tier that just wraps
rem whatever's already on the machine. No consent gate (see :try_embed_fallback's header
rem comment for why). See docs/agent-interconnect.md "Standalone Python-download tier" for the
rem full ordering rationale.
call :try_embed_fallback
if not errorlevel 1 (
  set "HP_ENV_READY=1"
  exit /b 0
)
rem REQ-009: venv fallback is attempted when conda and embed both fail; HP_ALLOW_VENV_FALLBACK
rem is deprecated/ignored.
call :try_venv_fallback
if not errorlevel 1 (
  set "HP_ENV_READY=1"
  exit /b 0
)
rem REQ-009/REQ-014: system Python is Tier 4 (by naming/history) and the absolute last resort --
rem the only tier gated by the REQ-014 consent prompt (touches the user's real environment), so
rem it stays final regardless of embed's move. HP_ALLOW_SYSTEM_FALLBACK is deprecated/ignored.
call :try_system_fallback
if not errorlevel 1 (
  set "HP_ENV_READY=1"
  exit /b 0
)
exit /b 0

:try_venv_fallback
call :log "[WARN] Attempting venv fallback..."
if "%HP_TEST_FORCE_VENV_FAIL%"=="1" (
  call :log "[TEST] HP_TEST_FORCE_VENV_FAIL: simulating venv creation failure."
  exit /b 1
)
call :resolve_system_python
if errorlevel 1 (
  call :log "[WARN] venv fallback: system Python not found."
  exit /b 1
)
if exist ".\.venv" rd /s /q ".\.venv" >nul 2>&1
if "%HP_TEST_FORCE_VENV_CREATE_FAIL%"=="1" goto :venv_create_retry
if defined HP_SYS_ARGS (
  "%HP_SYS_CMD%" %HP_SYS_ARGS% -m venv .\.venv >> "%LOG%" 2>&1
) else (
  "%HP_SYS_CMD%" -m venv .\.venv >> "%LOG%" 2>&1
)
if not errorlevel 1 goto :venv_create_ok
rem REQ-023b: "python -m venv" (which requires ensurepip) can fail outright on a stripped-down
rem host Python that is missing ensurepip -- a commonly cited real-world failure mode. Retry
rem once with --without-pip (which does not need ensurepip) and manually bootstrap pip via
rem get-pip.py, mirroring the existing Miniconda/uv download-with-fallback pattern
rem (:download_get_pip). Goto-based per "Provider-cascade dispatch is goto-based on purpose" in
rem docs/agent-lessons-learned.md.
:venv_create_retry
call :log "[WARN] venv fallback: python -m venv failed; retrying once with --without-pip."
if exist ".\.venv" rd /s /q ".\.venv" >nul 2>&1
if defined HP_SYS_ARGS (
  "%HP_SYS_CMD%" %HP_SYS_ARGS% -m venv .\.venv --without-pip >> "%LOG%" 2>&1
) else (
  "%HP_SYS_CMD%" -m venv .\.venv --without-pip >> "%LOG%" 2>&1
)
if errorlevel 1 (
  call :log "[WARN] venv fallback: python -m venv --without-pip also failed."
  exit /b 1
)
set "HP_PY=%CD%\.venv\Scripts\python.exe"
if not exist "%HP_PY%" (
  call :log "[WARN] venv fallback: interpreter missing after --without-pip creation."
  exit /b 1
)
call :download_get_pip
if not exist "%HP_GETPIP_PY%" (
  call :log "[WARN] venv fallback: get-pip.py download failed; venv has no pip."
  exit /b 1
)
"%HP_PY%" "%HP_GETPIP_PY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  del "%HP_GETPIP_PY%" >nul 2>&1
  call :log "[WARN] venv fallback: get-pip.py bootstrap failed."
  exit /b 1
)
del "%HP_GETPIP_PY%" >nul 2>&1
call :log "[INFO] venv fallback: pip bootstrapped successfully via get-pip.py."
goto :venv_create_pip_ready
:venv_create_ok
set "HP_PY=%CD%\.venv\Scripts\python.exe"
if not exist "%HP_PY%" (
  call :log "[WARN] venv fallback: interpreter missing after creation."
  exit /b 1
)
:venv_create_pip_ready
rem REQ-023: canary probe -- a venv can be "created" (directory + exe present) yet still be
rem non-functional (missing DLLs, broken symlinks, execution-policy blocks). Verify the fresh
rem interpreter actually runs before declaring success, so a silently broken venv doesn't reach
rem PyInstaller only to fail later with a more confusing error. Goto-based (not nested inside
rem an if/else block) per "Provider-cascade dispatch is goto-based on purpose" in
rem docs/agent-lessons-learned.md, so the probe call + %ERRORLEVEL% read is never frozen by
rem cmd's parse-time %VAR% expansion.
if "%HP_TEST_FORCE_VENV_CANARY_FAIL%"=="1" goto :venv_canary_fail
"%HP_PY%" -c "import sys" >nul 2>&1
if errorlevel 1 goto :venv_canary_fail
goto :venv_canary_ok
:venv_canary_fail
call :log "[WARN] venv fallback: interpreter created but failed canary probe (import sys)."
rem derived requirement: HP_PY was set above to the venv interpreter path in preparation for
rem success, but a failed canary probe must not leak it forward -- a later gate
rem (:after_env_mode_selection's "if not defined HP_PY") would otherwise treat this failed tier
rem as if a real provider had been selected, silently proceeding with a broken interpreter
rem instead of reaching :die. Exact mirror of the :try_system_fallback fix. See
rem docs/agent-lessons-learned.md "A declined/failed fallback tier must clear HP_PY".
set "HP_PY="
exit /b 1
:venv_canary_ok
set "HP_ENV_MODE=venv"
set "HP_BOOTSTRAP_STATE=venv_env"
set "HP_SKIP_PIPREQS="
call :log "[INFO] venv fallback ready: %HP_PY%"
call :log "[BOOT] REQ-009: Selected Python provider: Local venv (fallback)."
exit /b 0

:try_system_fallback
call :log "[WARN] Attempting system Python fallback (degraded)..."
call :resolve_system_python
if errorlevel 1 (
  call :log "[WARN] system fallback: interpreter not available."
  exit /b 1
)
set "HP_PY=%HP_SYS_EXE%"
if not exist "%HP_PY%" (
  call :log "[WARN] system fallback: resolved interpreter path missing."
  set "HP_PY="
  exit /b 1
)
rem REQ-014: consent gate before using global system Python.
call :system_python_consent_gate
if errorlevel 1 (
  call :log "[INFO] REQ-014: System Python fallback aborted: consent not granted."
  rem derived requirement: HP_PY was set above to prepare for a possible accept, but
  rem a decline must not leak it forward -- a later gate, :after_env_mode_selection's
  rem own "if not defined HP_PY" check, would otherwise treat this exhausted tier as if a real
  rem provider had been selected, silently proceeding with a stale interpreter path
  rem instead of reaching :die. See docs/agent-lessons-learned.md.
  set "HP_PY="
  exit /b 1
)
set "HP_ENV_MODE=system"
set "HP_BOOTSTRAP_STATE=degraded_env"
set "HP_SKIP_PIPREQS=1"
call :log "[INFO] System fallback using %HP_PY%"
call :log "[BOOT] REQ-009: Selected Python provider: System Python (degraded)."
exit /b 0

:resolve_system_python
set "HP_SYS_CMD="
set "HP_SYS_ARGS="
set "HP_SYS_EXE="
where python >nul 2>&1 && set "HP_SYS_CMD=python"
if not defined HP_SYS_CMD (
  where py >nul 2>&1 && (set "HP_SYS_CMD=py" & set "HP_SYS_ARGS=-3")
)
if not defined HP_SYS_CMD exit /b 1
set "HP_SYS_TMP=~sys_exe.txt"
if exist "%HP_SYS_TMP%" del "%HP_SYS_TMP%" >nul 2>&1
if defined HP_SYS_ARGS (
  "%HP_SYS_CMD%" %HP_SYS_ARGS% -c "import sys;print(sys.executable)" > "%HP_SYS_TMP%" 2>nul
) else (
  "%HP_SYS_CMD%" -c "import sys;print(sys.executable)" > "%HP_SYS_TMP%" 2>nul
)
if exist "%HP_SYS_TMP%" (
  set /p HP_SYS_EXE=<"%HP_SYS_TMP%"
  del "%HP_SYS_TMP%" >nul 2>&1
)
if not defined HP_SYS_EXE exit /b 1
exit /b 0

:try_embed_fallback
rem REQ-009 Tier 5: last-resort fallback when uv, conda, venv, and system all failed (or no
rem ambient interpreter exists at all). Unlike system Python, embed is a private, checksummed,
rem bootstrapper-controlled extraction -- no REQ-014-style consent gate, matching venv's
rem zero-friction treatment, not system's. Two stages: PowerShell (~embed_extract.ps1) always
rem fetches ONE hardcoded "latest" version with no per-request branching; Python
rem (~embed_pyver_check.py), running under that fresh interpreter, checks PYSPEC (already
rem computed earlier by ~detect_python.py -- the same value uv/conda already honor) and re-fetches
rem a different version via its own urllib/hashlib/zipfile if requested. See
rem docs/agent-interconnect.md "Standalone Python-download tier" for the full design rationale,
rem including why this is deliberately NOT all-PowerShell.
call :log "[WARN] Attempting embedded Python download (REQ-009 Tier 5)..."
if "%HP_TEST_FORCE_EMBED_FAIL%"=="1" (
  call :log "[TEST] HP_TEST_FORCE_EMBED_FAIL: simulating embed tier failure."
  exit /b 1
)
rem derived requirement: HP_TEST_FORCE_EMBED_REAL punches a narrow hole through the offline
rem gate for this tier only, mirroring HP_TEST_FORCE_VENV_CREATE_FAIL's exception for
rem :download_get_pip -- lets CI exercise the real embed download while HP_OFFLINE_MODE=1 still
rem blocks the earlier uv/conda/venv tiers' unrelated downloads in the same test run.
if "%HP_OFFLINE_MODE%"=="1" if not "%HP_TEST_FORCE_EMBED_REAL%"=="1" (
  call :log "[WARN] embed fallback: offline mode; cannot download embedded Python."
  exit /b 1
)
set "HP_EMBED_URL=https://www.python.org/ftp/python/%HP_EMBED_LATEST_PATCH%/python-%HP_EMBED_LATEST_PATCH%-embed-amd64.zip"
set "HP_EMBED_ZIP=%TEMP%\python-%HP_EMBED_LATEST_PATCH%-embed-amd64.zip"
set "HP_EMBED_DIR=%HP_SCRIPT_ROOT%~embed_python"
rem derived requirement: a single retry of the WHOLE download+verify cycle on either a download
rem failure or a checksum mismatch -- mirrors the existing transient-network-retry pattern used
rem by :try_conda_create/:conda_bulk_install (REQ-022) elsewhere in this file. A checksum mismatch
rem here does not necessarily mean a bad pin; it can mean a truncated/corrupted download, so
rem redownloading (not just re-verifying) is the correct remedy. Goto-based, not nested inside a
rem parenthesized if/else, per "Provider-cascade dispatch is goto-based on purpose" in
rem docs/agent-lessons-learned.md.
set "HP_EMBED_DL_ATTEMPT=0"
:embed_dl_retry
set /a HP_EMBED_DL_ATTEMPT+=1
if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1
rem derived requirement: HP_TEST_FORCE_EMBED_DL_FAIL_ONCE (CLAUDE.md former Active Backlog item
rem 12) deterministically fails ONLY the first attempt, without touching the real network, so
rem this genuine mid-download-failure-then-retry-once path gets CI coverage -- mirrors
rem HP_TEST_FORCE_CONDA_CREATE_NETWORK_FAIL's established one-shot-then-succeed pattern. Checked
rem BEFORE the real curl/PowerShell calls and cleared immediately, so attempt 2 always goes
rem through for real.
if "%HP_TEST_FORCE_EMBED_DL_FAIL_ONCE%"=="1" if "%HP_EMBED_DL_ATTEMPT%"=="1" goto :embed_dl_test_fail_once
call :log "[INFO] Downloading embedded Python %HP_EMBED_LATEST_PATCH% from %HP_EMBED_URL%..."
curl --fail -L --retry 3 --retry-delay 5 --max-time 120 "%HP_EMBED_URL%" -o "%HP_EMBED_ZIP%" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%HP_EMBED_ZIP%" goto :embed_dl_ok
echo *** curl download failed, trying PowerShell...
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%HP_EMBED_URL%' -OutFile '%HP_EMBED_ZIP%' -UseBasicParsing } catch { exit 1 }" >> "%LOG%" 2>&1
if not errorlevel 1 if exist "%HP_EMBED_ZIP%" goto :embed_dl_ok
goto :embed_dl_attempt_failed
:embed_dl_test_fail_once
call :log "[TEST] HP_TEST_FORCE_EMBED_DL_FAIL_ONCE: simulating download failure on attempt 1 (no network touched)."
set "HP_TEST_FORCE_EMBED_DL_FAIL_ONCE="
:embed_dl_attempt_failed
if %HP_EMBED_DL_ATTEMPT% LSS 2 (
  call :log "[WARN] embed fallback: download failed; retrying once."
  goto :embed_dl_retry
)
call :log "[WARN] embed fallback: download failed (both curl and PowerShell)."
exit /b 1
:embed_dl_ok
call :emit_from_base64 "~embed_extract.ps1" HP_EMBED_EXTRACT
if errorlevel 1 (
  call :log "[WARN] embed fallback: could not write ~embed_extract.ps1."
  if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1
  exit /b 1
)
set "HP_EMBED_PY="
for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -File "~embed_extract.ps1" "%HP_EMBED_ZIP%" "%HP_EMBED_LATEST_SHA256%" "%HP_EMBED_DIR%" 2^>^> "%LOG%"`) do set "HP_EMBED_PY=%%P"
if exist "~embed_extract.ps1" del "~embed_extract.ps1" >nul 2>&1
if exist "%HP_EMBED_ZIP%" del "%HP_EMBED_ZIP%" >nul 2>&1
if not exist "%HP_EMBED_PY%" (
  if %HP_EMBED_DL_ATTEMPT% LSS 2 (
    call :log "[WARN] embed fallback: checksum verification or extraction failed; retrying download once."
    goto :embed_dl_retry
  )
  call :log "[WARN] embed fallback: checksum verification or extraction failed."
  exit /b 1
)
call :log "[INFO] embed fallback: %HP_EMBED_LATEST_PATCH% extracted and verified."

rem --- Python stage: only place per-request version logic lives (deliberately not PowerShell
rem a second time). A failure here is non-fatal -- the "latest" interpreter from the stage above
rem is kept and used as-is; only a genuine version *mismatch* is lost, not the whole tier.
rem derived requirement: this block used to be one big parenthesized "if not errorlevel 1 ( ... )"
rem block. A for /f loop inside it set HP_EMBED_SWAP_DIR/_TAG/_MINOR, and code later in the SAME
rem block read %HP_EMBED_SWAP_DIR% to decide whether to swap -- but CMD's parse-time %VAR%
rem expansion substitutes every %VAR% in a parenthesized block using the value from BEFORE the
rem block began, not a value a for /f loop set earlier in the same block's own execution. Since
rem HP_EMBED_SWAP_DIR was never set before this point, that read was always empty, so the swap
rem NEVER executed regardless of what the Python stage actually requested -- the entire
rem "pull latest, then swap to the user's requested version" feature was dead code. Fixed via
rem goto-based dispatch (see "Provider-cascade dispatch is goto-based on purpose" in
rem docs/agent-lessons-learned.md) so every %VAR% read below reflects its true runtime value.
call :emit_from_base64 "~embed_pyver_check.py" HP_EMBED_PYVER_CHECK
if errorlevel 1 goto :embed_pyver_check_skip
set "HP_EMBED_CHECK_OUT=~embed_pyver_check.txt"
if exist "%HP_EMBED_CHECK_OUT%" del "%HP_EMBED_CHECK_OUT%" >nul 2>&1
"%HP_EMBED_PY%" "~embed_pyver_check.py" "%HP_EMBED_DIR%" > "%HP_EMBED_CHECK_OUT%" 2>> "%LOG%"
if exist "~embed_pyver_check.py" del "~embed_pyver_check.py" >nul 2>&1
set "HP_EMBED_SWAP_TAG="
set "HP_EMBED_SWAP_MINOR="
set "HP_EMBED_SWAP_DIR="
for /f "usebackq tokens=1,2,3 delims=|" %%A in ("%HP_EMBED_CHECK_OUT%") do (
  set "HP_EMBED_SWAP_TAG=%%A"
  set "HP_EMBED_SWAP_MINOR=%%B"
  set "HP_EMBED_SWAP_DIR=%%C"
)
if exist "%HP_EMBED_CHECK_OUT%" del "%HP_EMBED_CHECK_OUT%" >nul 2>&1
rem derived requirement: the version-check process (the "%HP_EMBED_PY%" call above) has
rem already fully exited by this point, so its file locks on HP_EMBED_DIR are released and
rem it is now safe to replace that directory -- swapping while that process was still running
rem would fail (Windows will not let a process delete/replace the files it is executing from),
rem which is exactly why the Python stage extracted into a sibling _swap directory instead of
rem overwriting HP_EMBED_DIR itself. See docs/agent-interconnect.md.
if not defined HP_EMBED_SWAP_DIR goto :embed_pyver_check_tagcheck
if not exist "%HP_EMBED_SWAP_DIR%\python.exe" goto :embed_pyver_check_tagcheck
rem derived requirement: rd /s /q can return before an AV/indexer file handle on HP_EMBED_DIR
rem fully releases (a real, if low-severity, Windows deletion race). Unlike a FILE move onto an
rem existing destination (atomic replace-or-noop), a DIRECTORY move onto a destination that still
rem exists does not fail cleanly -- it silently NESTS the source inside the destination instead
rem (HP_EMBED_SWAP_DIR ends up at HP_EMBED_DIR\<its own name>, not replacing HP_EMBED_DIR's
rem contents). A post-hoc "does HP_EMBED_DIR\python.exe exist" check cannot detect this: if rd
rem fails, the STALE prior python.exe is still sitting at HP_EMBED_DIR's top level regardless of
rem what move did, so the check reads success either way. Fixed by gating move on rd having
rem actually cleared the destination first -- move then only ever runs onto a nonexistent target
rem (pure rename, no nesting possible), which makes the post-check reliable again. Retries the
rem pair up to 3 total attempts with a short pause, so a rare transient lock doesn't needlessly
rem fail the last-resort tier. Uses ping (not timeout /t) for the pause -- this file already has
rem a proven-safe idiom for exactly this at line 1461's VISA-detection delay.
set "HP_EMBED_SWAP_ATTEMPT=0"
:embed_swap_retry
set /a HP_EMBED_SWAP_ATTEMPT+=1
rd /s /q "%HP_EMBED_DIR%" >nul 2>&1
if exist "%HP_EMBED_DIR%" goto :embed_swap_rd_failed
move /y "%HP_EMBED_SWAP_DIR%" "%HP_EMBED_DIR%" >nul 2>&1
if exist "%HP_EMBED_DIR%\python.exe" (
  call :log "[INFO] embed fallback: swapped to requested Python %HP_EMBED_SWAP_MINOR%."
  goto :embed_pyver_check_tagcheck
)
:embed_swap_rd_failed
if %HP_EMBED_SWAP_ATTEMPT% LSS 3 (
  ping -n 2 127.0.0.1 >nul 2>&1
  goto :embed_swap_retry
)
call :log "[WARN] embed fallback: swap move failed; interpreter may be missing."
:embed_pyver_check_tagcheck
if /i "%HP_EMBED_SWAP_TAG%"=="fellback" call :log "[WARN] REQ-009: requested Python not in embed table; using %HP_EMBED_SWAP_MINOR% instead."
:embed_pyver_check_skip
set "HP_EMBED_PY=%HP_EMBED_DIR%\python.exe"
if not exist "%HP_EMBED_PY%" (
  call :log "[WARN] embed fallback: interpreter missing after version-check stage."
  exit /b 1
)

rem --- bootstrap pip via get-pip.py (reuses the existing REQ-023b download) ---
call :download_get_pip
if not exist "%HP_GETPIP_PY%" (
  call :log "[WARN] embed fallback: get-pip.py download failed."
  exit /b 1
)
"%HP_EMBED_PY%" "%HP_GETPIP_PY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  del "%HP_GETPIP_PY%" >nul 2>&1
  call :log "[WARN] embed fallback: get-pip.py bootstrap failed."
  exit /b 1
)
del "%HP_GETPIP_PY%" >nul 2>&1

rem --- canary probe (matches REQ-023's venv canary probe) ---
"%HP_EMBED_PY%" -c "import sys, pip" >nul 2>&1
if errorlevel 1 (
  call :log "[WARN] embed fallback: interpreter created but failed canary probe (import sys, pip)."
  exit /b 1
)

set "HP_ENV_MODE=embed"
set "HP_PY=%HP_EMBED_PY%"
set "HP_BOOTSTRAP_STATE=embed_env"
set "HP_SKIP_PIPREQS="
call :log "[INFO] embed fallback ready: %HP_PY%"
call :log "[BOOT] REQ-009: Selected Python provider: Embedded Python (python.org)."
exit /b 0

:append_env_mode_row
if not defined HP_NDJSON exit /b 0
if not defined HP_PY exit /b 0
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$mode = [Environment]::GetEnvironmentVariable('HP_ENV_MODE');" ^
  "$py = [Environment]::GetEnvironmentVariable('HP_PY');" ^
  "if (-not $mode) { $mode = 'unknown' }" ^
  "$row = @{ id='env.mode'; pass=$true; details=@{ mode=$mode; py=$py } } | ConvertTo-Json -Compress -Depth 8;" ^
  "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
exit /b 0

:update_find_entry_abs
rem derived requirement: CI skip lane could change the working directory while probing
rem for helper crumbs. Recompute the absolute helper path each time so commands like
rem `py -3` never see a dangling relative path.
if exist "%HP_FIND_ENTRY_NAME%" (
  for %%F in ("%HP_FIND_ENTRY_NAME%") do set "HP_FIND_ENTRY_ABS=%%~fF"
) else if defined HP_SCRIPT_ROOT (
  rem derived requirement: helper lookups must stay rooted to the bootstrapper
  rem directory even if callers pushd elsewhere. Use HP_SCRIPT_ROOT so the CI
  rem skip lane never feeds CMD a dangling relative path.
  set "HP_FIND_ENTRY_ABS=%HP_SCRIPT_ROOT%%HP_FIND_ENTRY_NAME%"
) else (
  set "HP_FIND_ENTRY_ABS=%CD%\%HP_FIND_ENTRY_NAME%"
)
exit /b 0

:verify_find_entry_helper
if "%HP_FIND_ENTRY_SYNTAX_OK%"=="1" exit /b 0
if "%HP_FIND_ENTRY_SYNTAX_OK%"=="0" exit /b 1
set "HP_HELPER_CMD="
set "HP_HELPER_ARGS="
set "HP_HELPER_SYNTAX_PASS=0"
if defined HP_PY if exist "%HP_PY%" set "HP_HELPER_CMD=%HP_PY%"
if not defined HP_HELPER_CMD (
  where python >nul 2>&1 && set "HP_HELPER_CMD=python"
)
if not defined HP_HELPER_CMD (
  where py >nul 2>&1 && (set "HP_HELPER_CMD=py" & set "HP_HELPER_ARGS=-3")
)
if defined HP_HELPER_CMD for %%C in ("%HP_HELPER_CMD%") do set "HP_HELPER_CMD=%%~C"
if defined HP_HELPER_ARGS for %%A in ("%HP_HELPER_ARGS%") do set "HP_HELPER_ARGS=%%~A"
if defined HP_HELPER_CMD (
  if not defined HP_HELPER_CMD_LOGGED (
    rem derived requirement: capture the helper command verbatim so future regressions can
    rem trace quoting issues without reproducing CI. Logged once per bootstrap run.
    if defined HP_HELPER_ARGS (
      >> "%LOG%" echo Helper command: "%HP_HELPER_CMD%" %HP_HELPER_ARGS% "%HP_FIND_ENTRY_ABS%"
    ) else (
      >> "%LOG%" echo Helper command: "%HP_HELPER_CMD%" "%HP_FIND_ENTRY_ABS%"
    )
    set "HP_HELPER_CMD_LOGGED=1"
  )
  if defined HP_HELPER_ARGS (
    "%HP_HELPER_CMD%" %HP_HELPER_ARGS% -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul
  ) else (
    "%HP_HELPER_CMD%" -m py_compile "%HP_FIND_ENTRY_ABS%" 1>nul 2>nul
  )
  if errorlevel 1 (
    set "HP_HELPER_SYNTAX_PASS=0"
  ) else (
    set "HP_HELPER_SYNTAX_PASS=1"
  )
) else (
  set "HP_HELPER_SYNTAX_PASS=0"
)
set "HP_FIND_ENTRY_SYNTAX_OK=%HP_HELPER_SYNTAX_PASS%"
call :append_helper_syntax_row
set "HP_HELPER_CMD="
set "HP_HELPER_ARGS="
if "%HP_HELPER_SYNTAX_PASS%"=="1" exit /b 0
exit /b 1

:append_helper_syntax_row
if "%HP_HELPER_SYNTAX_EMITTED%"=="1" exit /b 0
if not defined HP_NDJSON (
  set "HP_HELPER_SYNTAX_EMITTED=1"
  exit /b 0
)
if "%HP_FIND_ENTRY_SYNTAX_OK%"=="" (
  set "HP_HELPER_SYNTAX_EMITTED=1"
  exit /b 0
)
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$flag = [Environment]::GetEnvironmentVariable('HP_FIND_ENTRY_SYNTAX_OK');" ^
  "if (-not $flag) { $flag = '0' }" ^
  "$ok = $flag -eq '1';" ^
  "$row = @{ id='helper.find_entry.syntax'; pass=$ok; details=@{ } } | ConvertTo-Json -Compress -Depth 8;" ^
  "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
set "HP_HELPER_SYNTAX_EMITTED=1"
exit /b 0

:emit_conda_probe_skip
if not defined HP_NDJSON exit /b 0
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$reason = [Environment]::GetEnvironmentVariable('HP_CONDA_PROBE_REASON');" ^
  "if (-not $reason) { $reason = 'not-requested' }" ^
  "$row = @{ id='conda.url'; pass=$true; details=@{ skipped=$true; reason=$reason; bytes=0 } } | ConvertTo-Json -Compress -Depth 8;" ^
  "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
exit /b 0

:probe_conda_url
set "HP_DL_PATH=~miniconda.exe"
if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1
curl -L --retry 3 --retry-delay 5 --max-time 120 "%HP_MINICONDA_URL%" -o "%HP_DL_PATH%" >> "%LOG%" 2>&1
if errorlevel 1 goto :probe_conda_url_fail
set "HP_DL_BYTES=0"
if exist "%HP_DL_PATH%" for %%S in ("%HP_DL_PATH%") do set "HP_DL_BYTES=%%~zS"
set "HP_DL_PASS=1"
if not defined HP_DL_BYTES set "HP_DL_BYTES=0"
for /f "tokens=*" %%B in ("%HP_DL_BYTES%") do set "HP_DL_BYTES=%%B"
for /f "tokens=*" %%B in ("%HP_MINICONDA_MIN_BYTES%") do set "HP_MIN_BYTES_SAFE=%%B"
if not defined HP_MIN_BYTES_SAFE set "HP_MIN_BYTES_SAFE=0"
set /a HP_BYTES_CHECK=%HP_DL_BYTES%
set /a HP_MIN_CHECK=%HP_MIN_BYTES_SAFE%
if %HP_BYTES_CHECK% LSS %HP_MIN_CHECK% set "HP_DL_PASS=0"
if "%HP_DL_PASS%"=="0" goto :probe_conda_url_fail_with_bytes
call :log "[INFO] Miniconda probe downloaded %HP_DL_BYTES% bytes."
if defined HP_NDJSON (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$row = @{ id='conda.url'; pass=$true; details=@{ bytes=%HP_DL_BYTES% } } | ConvertTo-Json -Compress -Depth 8;" ^
    "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
)
if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1
exit /b 0

:probe_conda_url_fail_with_bytes
if defined HP_NDJSON (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$row = @{ id='conda.url'; pass=$false; details=@{ bytes=%HP_DL_BYTES% } } | ConvertTo-Json -Compress -Depth 8;" ^
    "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
)
if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1
exit /b 1

:probe_conda_url_fail
set "HP_DL_BYTES=0"
if defined HP_NDJSON (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$row = @{ id='conda.url'; pass=$false; details=@{ bytes=0 } } | ConvertTo-Json -Compress -Depth 8;" ^
    "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
)
if exist "%HP_DL_PATH%" del "%HP_DL_PATH%" >nul 2>&1
exit /b 1

:extract_pep723_requirements
set "HP_PEP723_IN=%~1"
set "HP_PEP723_OUT=%~2"
if exist "%HP_PEP723_OUT%" del "%HP_PEP723_OUT%" >nul 2>&1
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$inside = $false; $deps = $false;" ^
  "Get-Content -LiteralPath '%HP_PEP723_IN%' | ForEach-Object {" ^
  "  $line = $_;" ^
  "  if (-not $inside) { if ($line -eq '# /// script') { $inside = $true }; return }" ^
  "  if ($line -eq '# ///') { $inside = $false; $deps = $false; return }" ^
  "  $trim = $line.Trim();" ^
  "  $compact = ($trim -replace '\s','');" ^
  "  if ($compact.StartsWith('#dependencies=') -and $compact.EndsWith('[')) { $deps = $true; return }" ^
  "  if ($deps -and $compact -eq '#]') { $deps = $false; return }" ^
  "  if ($deps -and $trim.StartsWith('# ""')) { $item = $trim.Substring(3).Trim(); if ($item.EndsWith('""')) { $item = $item.Substring(0, $item.Length - 1) }; $item }" ^
  "} | Set-Content -LiteralPath '%HP_PEP723_OUT%' -Encoding ASCII" >> "%LOG%" 2>&1
exit /b %errorlevel%

:determine_entry
set "HP_ENTRY="
set "HP_ENTRY_CMD="
set "HP_ENTRY_ARGS="
if not "%~1"=="" (
  rem REQ-011: %~dp1 = caller's argument directory; %~dp0 = batch script directory -- both include a trailing \
  if /i not "%~dp1"=="%~dp0" (
    echo [ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder for environment cleanliness.
    call :log "[ERROR] REQ-011: Dragged files must reside in the bootstrapper root folder."
    exit /b 11
  )
  if exist "%~1" (
    rem derived requirement: use the %~1 parameter directly, not %MAIN_FILE%. Inside this
    rem parenthesized block %MAIN_FILE% expands at parse time -- before "set MAIN_FILE" runs --
    rem which yielded an empty HP_ENTRY and an empty "Using drag-and-drop file:" message.
    rem %~1 is the call parameter and expands to the argument value, so both are correct.
    set "HP_ENTRY=%~1"
    if not defined HP_DRAG_MSG_EMITTED (
      echo *** Using drag-and-drop file: %~1
      set "HP_DRAG_MSG_EMITTED=1"
    )
    exit /b 0
  )
)
call :emit_from_base64 "~find_entry.py" HP_FIND_ENTRY
if errorlevel 1 exit /b 1
call :update_find_entry_abs
call :verify_find_entry_helper
if errorlevel 1 exit /b 1
if defined HP_PY if exist "%HP_PY%" set "HP_ENTRY_CMD=%HP_PY%"
if not defined HP_ENTRY_CMD if defined CONDA_BASE_PY if exist "%CONDA_BASE_PY%" set "HP_ENTRY_CMD=%CONDA_BASE_PY%"
if not defined HP_ENTRY_CMD (
  where python >nul 2>&1 && set "HP_ENTRY_CMD=python"
)
if not defined HP_ENTRY_CMD (
  where py >nul 2>&1 && (set "HP_ENTRY_CMD=py" & set "HP_ENTRY_ARGS=-3")
)
if defined HP_ENTRY_CMD for %%C in ("%HP_ENTRY_CMD%") do set "HP_ENTRY_CMD=%%~C"
if defined HP_ENTRY_ARGS for %%A in ("%HP_ENTRY_ARGS%") do set "HP_ENTRY_ARGS=%%~A"
if not defined HP_ENTRY_CMD exit /b 0
rem derived requirement: maintain split helper calls so `py -3` and bare `python` both expand
rem without producing `python"` tokens. This mirrors the CI skip logic above and uses a crumb file
rem so Windows never merges tokens when arguments are empty.
set "HP_ENTRY_CRUMB=~entry.crumb"
if exist "%HP_ENTRY_CRUMB%" del "%HP_ENTRY_CRUMB%" >nul 2>&1
if defined HP_ENTRY_ARGS (
  "%HP_ENTRY_CMD%" %HP_ENTRY_ARGS% "%HP_FIND_ENTRY_ABS%" > "%HP_ENTRY_CRUMB%" 2>> "%LOG%"
) else (
  "%HP_ENTRY_CMD%" "%HP_FIND_ENTRY_ABS%" > "%HP_ENTRY_CRUMB%" 2>> "%LOG%"
)
rem REQ-002: capture find_entry's exit code immediately -- AMBIGUOUS_RC (3) marks the
rem alphabetical fallback (multiple files, no clear winner), the only case that offers
rem the timed interactive picker. Must read %ERRORLEVEL% before any other command.
set "HP_FIND_RC=%ERRORLEVEL%"
if exist "%HP_ENTRY_CRUMB%" (
  for /f "usebackq delims=" %%M in ("%HP_ENTRY_CRUMB%") do if not defined HP_ENTRY set "HP_ENTRY=%%M"
  del "%HP_ENTRY_CRUMB%" >nul 2>&1
)
if not defined HP_ENTRY set "HP_ENTRY="
if defined HP_ENTRY if "%HP_FIND_RC%"=="3" call :pick_entry_interactive
set "HP_FIND_RC="
exit /b 0

:pick_entry_interactive
rem REQ-002: timed interactive entry picker for the ambiguous case. The default
rem (Enter / timeout) is find_entry's alphabetical pick (HP_ENTRY). Skipped when
rem non-interactive: NOINPUT, HP_NONINTERACTIVE, or CI (HP_CI_LANE) -- unless
rem HP_TEST_FORCE_PICKER forces the path (it degrades to the default with no console).
if defined NOINPUT exit /b 0
if defined HP_NONINTERACTIVE exit /b 0
if defined HP_CI_LANE if not defined HP_TEST_FORCE_PICKER exit /b 0
rem choice.exe drives the timed prompt; on a stripped image without it, keep the default.
where choice >nul 2>&1 || exit /b 0
if exist "~entry.menu" del "~entry.menu" >nul 2>&1
(for /f "delims=" %%F in ('dir /b /a-d *.py 2^>nul ^| findstr /v /b /c:"~" ^| sort') do echo %%F) > "~entry.menu"
set "HP_PICK_N=0"
for /f "usebackq delims=" %%F in ("~entry.menu") do set /a HP_PICK_N+=1
if %HP_PICK_N% LSS 2 ( del "~entry.menu" >nul 2>&1 & exit /b 0 )
if %HP_PICK_N% GTR 9 (
  call :log "[INFO] REQ-002: %HP_PICK_N% candidates exceed picker limit; keeping %HP_ENTRY% (alphabetical)."
  echo   Tip: to avoid the alphabetical fallback next time, do any one of these:
  echo     1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run
  echo        that file directly. It must be in this same folder.
  echo     2. Rename your main script to one of: main.py, app.py, run.py, or cli.py.
  echo     3. Give exactly one script an  if __name__ == "__main__":  block.
  del "~entry.menu" >nul 2>&1
  exit /b 0
)
echo.
echo Multiple Python files detected -- no clear entry point, so please choose one to run:
set "HP_PICK_I=0"
set "HP_PICK_DEFAULT=1"
for /f "usebackq delims=" %%F in ("~entry.menu") do call :pick_menu_line "%%F"
echo.
echo   Tip: to skip this question next time, do any one of these:
echo     1. Drag a .py file onto run_setup.bat -- drop it on the batch file icon to run
echo        that file directly. It must be in this same folder.
echo     2. Rename your main script to one of: main.py, app.py, run.py, or cli.py.
echo     3. Give exactly one script an  if __name__ == "__main__":  block.
echo   If you do nothing, the alphabetically-first file is used: %HP_ENTRY%
echo.
set "HP_PICK_T=30"
if defined HP_TEST_FORCE_PICKER set "HP_PICK_T=2"
choice /C 123456789 /N /T %HP_PICK_T% /D %HP_PICK_DEFAULT% /M "Type a number 1-%HP_PICK_N%, or wait %HP_PICK_T%s for the default [%HP_PICK_DEFAULT%]: "
set "HP_PICK_SEL=%ERRORLEVEL%"
if %HP_PICK_SEL% GEQ 1 if %HP_PICK_SEL% LEQ %HP_PICK_N% (
  set "HP_PICK_I=0"
  for /f "usebackq delims=" %%F in ("~entry.menu") do call :pick_apply_line "%%F"
)
del "~entry.menu" >nul 2>&1
call :log "[INFO] REQ-002: Picker entry selected: %HP_ENTRY%"
set "HP_PICK_N="
set "HP_PICK_I="
set "HP_PICK_SEL="
set "HP_PICK_DEFAULT="
set "HP_PICK_T="
exit /b 0

:pick_menu_line
set /a HP_PICK_I+=1
echo   [%HP_PICK_I%] %~1
if /i "%~1"=="%HP_ENTRY%" set "HP_PICK_DEFAULT=%HP_PICK_I%"
exit /b 0

:pick_apply_line
set /a HP_PICK_I+=1
if "%HP_PICK_I%"=="%HP_PICK_SEL%" set "HP_ENTRY=%~1"
exit /b 0

:record_chosen_entry
rem %~1 is the RELATIVE crumb (what we want to show users and tests)
set "HP_CRUMB=%~1"
if "%HP_CRUMB%"=="" exit /b 0
set "HP_CRUMB_SHOW=%HP_CRUMB%"
set "HP_CRUMB_DRIVE="
for %%A in ("%HP_CRUMB_SHOW%") do (
  if not "%%~dA"=="" set "HP_CRUMB_DRIVE=%%~dA"
)
if not defined HP_CRUMB_DRIVE (
  set "HP_CRUMB_FIRST=%HP_CRUMB_SHOW:~0,1%"
  if not "%HP_CRUMB_FIRST%"=="\" (
    if not "%HP_CRUMB_FIRST%"=="/" (
      set "HP_CRUMB_PREFIX2=%HP_CRUMB_SHOW:~0,2%"
      if /I not "%HP_CRUMB_PREFIX2%"==".\" (
        if not "%HP_CRUMB_PREFIX2%"=="./" (
          if not "%HP_CRUMB_PREFIX2%"==".." (
            set "HP_CRUMB_SHOW=.\%HP_CRUMB_SHOW%"
          )
        )
      )
    )
  )
)
set "HP_CRUMB=%HP_CRUMB_SHOW%"
set "HP_CRUMB_DRIVE="
set "HP_CRUMB_FIRST="
set "HP_CRUMB_PREFIX2="
set "HP_CRUMB_SHOW="

rem Echo to console (no punctuation at end)
echo Chosen entry: %HP_CRUMB%
rem Append same line to setup log
>> "%LOG%" echo Chosen entry: %HP_CRUMB%
call :log "[BOOT] REQ-002: Entry selected: %HP_CRUMB%"

rem If we also need an absolute path for execution, set HP_ENTRY elsewhere
rem and keep the echo outside any ( ... ) block.
exit /b 0
:compute_interactive_run
rem Slice 2b-C: single interactivity determination shared by the fail-fast probe at both
rem untimed user-code launch points (:try_fast_exe, :verify_no_exe_interpreter). Mirrors
rem :pick_entry_interactive's three non-interactivity signals (NOINPUT, HP_NONINTERACTIVE,
rem HP_CI_LANE) so a real double-click user gets the new probe with zero flags required,
rem while every CI/automation signal keeps today's plain-redirect behavior byte-for-byte
rem unchanged (self.fastpath / self.exe.fastpath.graceful stay deterministic).
rem HP_TEST_FORCE_INTERACTIVE_PROBE=1 forces the probe branch under HP_CI_LANE for
rem dedicated, deterministic CI coverage of the new state machine (mirrors HP_TEST_FORCE_PICKER).
set "HP_INTERACTIVE_RUN=1"
if defined NOINPUT set "HP_INTERACTIVE_RUN="
if defined HP_NONINTERACTIVE set "HP_INTERACTIVE_RUN="
if defined HP_CI_LANE if not defined HP_TEST_FORCE_INTERACTIVE_PROBE set "HP_INTERACTIVE_RUN="
exit /b 0
:run_failfast_probe
rem Slice 2b-C: shared fail-fast probe for the two untimed user-code launch points. Reuses
rem :run_exe_smokerun's ProcessStartInfo + ReadToEndAsync pattern (preserves ~run.out.txt /
rem ~run.err.txt for existing consumers -- envsmoke's 'smoke-ok' token, spaced-path dist\
rem token capture) but replaces the single 30s-cap-then-Kill() wait with a two-stage wait
rem inside ~failfast_probe.ps1 (HP_FAILFAST_PROBE): WaitForExit(HP_FAILFAST_PROBE_MS)
rem classifies a fast exit vs. still-running; if still running, a SECOND, UNBOUNDED
rem WaitForExit() follows and the process is NEVER killed -- this is the only difference
rem from :run_exe_smokerun, which stays the sole place in this file allowed to force-kill
rem (the fresh-build verification run). Caller sets HP_PROBE_EXE / HP_PROBE_ARGS ([REQ-026]:
rem a full, pre-quoted Windows Arguments string used verbatim -- the CALLER quotes each token,
rem not this subroutine) / HP_PROBE_CWD before calling; %1 is a short site tag
rem ('fastpath'|'interpreter'|'checkpoint' -- the last added by :run_postexec_checkpoint,
rem Slice 2b-C's post-execution checkpoint) used only for the NDJSON row and log text. Always
rem leaves HP_SMOKE_RC set to the true final exit code and HP_PROBE_EXCEEDED set (1) iff the probe
rem window was exceeded -- the caller decides what that means (:try_fast_exe discards a
rem cached EXE only when NOT exceeded; :verify_no_exe_interpreter has no cached artifact and
rem just reports the final outcome either way). Caller may optionally set HP_PROBE_OUT /
rem HP_PROBE_ERR to redirect the captured stdout/stderr somewhere other than the default
rem ~run.out.txt / ~run.err.txt -- :run_postexec_checkpoint uses this so its elective SECOND
rem run never overwrites the FIRST (real) verification run's captured output.
set "HP_PROBE_SITE=%~1"
set "HP_PROBE_EXCEEDED="
set "HP_SMOKE_RC="
set "HP_PROBE_PS=~failfast_probe.ps1"
if not defined HP_PROBE_OUT set "HP_PROBE_OUT=~run.out.txt"
if not defined HP_PROBE_ERR set "HP_PROBE_ERR=~run.err.txt"
if not defined HP_PROBE_RESULT set "HP_PROBE_RESULT=~probe_result.txt"
if exist "%HP_PROBE_PS%" del "%HP_PROBE_PS%" >nul 2>&1
rem pre-truncate: the helper only writes these once, at process exit, so without this an
rem unbounded ALIVE-AT-PROBE wait would leave stale prior-run content lingering for its
rem full duration with no indication it is stale.
if exist "%HP_PROBE_OUT%" del "%HP_PROBE_OUT%" >nul 2>&1
if exist "%HP_PROBE_ERR%" del "%HP_PROBE_ERR%" >nul 2>&1
if exist "%HP_PROBE_RESULT%" del "%HP_PROBE_RESULT%" >nul 2>&1
call :emit_from_base64 "%HP_PROBE_PS%" HP_FAILFAST_PROBE
if errorlevel 1 (
  rem Extremely rare -- disk/permission failure writing a work file; mirror :try_fast_exe's own
  rem emit-failure convention of skipping gracefully rather than hand-rolling an unsafe manual
  rem launch here -- [REQ-026]: HP_PROBE_ARGS is now a full, pre-quoted Windows Arguments string
  rem the CALLER is responsible for quoting correctly, see this subroutine's own header
  rem comment; a direct cmd invocation here would still need that same care.
  rem HP_SMOKE_RC stays unset; the safety net below turns that into -1 so callers still see a
  rem defined, non-zero, non-"exceeded" outcome -- :try_fast_exe's discard-and-rebuild fires.
  call :log "[WARN] Fail-fast probe: could not emit ~failfast_probe.ps1; treating as a failed run."
) else (
  rem Invoked DIRECTLY, no for /f/backtick stdout capture -- the script's own live-teed child
  rem output reaches the console instead of being captured and silently swallowed. The result is
  rem read afterward from HP_PROBE_RESULT, a static file -- safe for /f target, unlike a live
  rem process's stdout. See docs/plan-cli-interactive-verification.md Finding 6.
  powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_PROBE_PS%"
  if exist "%HP_PROBE_RESULT%" (
    for /f "usebackq tokens=1,2 delims=|" %%A in ("%HP_PROBE_RESULT%") do (
      set "HP_PROBE_EXCEEDED=%%A"
      set "HP_SMOKE_RC=%%B"
    )
  )
  if exist "%HP_PROBE_PS%" del "%HP_PROBE_PS%" >nul 2>&1
  if exist "%HP_PROBE_RESULT%" del "%HP_PROBE_RESULT%" >nul 2>&1
)
if "%HP_PROBE_EXCEEDED%"=="0" set "HP_PROBE_EXCEEDED="
if not defined HP_SMOKE_RC set "HP_SMOKE_RC=-1"
call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%"
if defined HP_PROBE_EXCEEDED (
  call :log "[INFO] Fail-fast probe: still running after %HP_FAILFAST_PROBE_MS%ms; this is your program's real run, not a rebuild trigger. If it has a GUI it may have opened minimized or on another window; if it is a background/console app with no window, that is expected. The bootstrapper is waiting for it to finish so it can report the final result -- it will not force-stop it."
)
if "%HP_SMOKE_RC%"=="0" (
  call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)"
) else (
  call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)"
)
if defined HP_NDJSON (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$c=[int]'%HP_SMOKE_RC%';$ex=[bool]'%HP_PROBE_EXCEEDED%';" ^
    "$r=[ordered]@{id='self.failfast.probe';pass=($c -eq 0);details=[ordered]@{site='%HP_PROBE_SITE%';exitCode=$c;probeExceeded=$ex;probeMs=[int]'%HP_FAILFAST_PROBE_MS%'}}|ConvertTo-Json -Compress -Depth 8;" ^
    "Add-Content -Path '%HP_NDJSON%' -Value $r -Encoding ASCII" >> "%LOG%" 2>&1
)
set "HP_PROBE_EXE="
set "HP_PROBE_ARGS="
set "HP_PROBE_CWD="
set "HP_PROBE_SITE="
set "HP_PROBE_PS="
set "HP_PROBE_OUT="
set "HP_PROBE_ERR="
exit /b 0
:run_postexec_checkpoint
rem REQ-018 (2b-C): post-execution checkpoint. The FIRST run (EXE smoke or no-EXE interpreter
rem run) has already happened and its [STATUS] telemetry has already been printed by the time
rem this is called -- this offers an ELECTIVE second run via the interpreter as a diagnostic
rem tool, never forced. Worth offering even after a reported SUCCESS: the first run's exit code 0
rem is not 100% confirmation the app actually worked correctly -- a frozen EXE can differ from
rem source in bundled resources, DLL binding, etc., see "Honest ambiguous-exit messaging" in
rem docs/agent-interconnect.md. The interpreter path is generally the more reliable one to catch a
rem real problem the EXE's own exit code alone would not surface. Declining (the default and only
rem outcome in CI/automation) leaves the run footprint at exactly one execution. Called
rem unconditionally after every verification
rem telemetry point (never after :try_fast_exe's fast-path reuse, which stays zero-friction by
rem design -- see docs/agent-interconnect.md). %1 is a short site tag ('exe'|'interpreter') used
rem only for the log lines below, so a reader can tell which verification path preceded this.
rem
rem Mirrors the existing 3-branch consent-gate pattern (:system_build_consent_gate,
rem :cascade_consent_gate, :system_python_consent_gate): echo the prompt UNCONDITIONALLY (so
rem prompt-text assertions see it even on auto-decline), then HP_TEST_CHECKPOINT_ANSWER
rem (override, checked FIRST so an explicit Y reaches the accept branch even under HP_CI_LANE)
rem -> HP_CI_LANE auto-decline -> interactive set /p. UNLIKE those 3 gates (each reached only on
rem a narrow edge-case path), this one fires on essentially every successful bootstrap run, so it
rem ALSO auto-declines on NOINPUT/HP_NONINTERACTIVE (the same signals :compute_interactive_run and
rem :pick_entry_interactive already treat as authoritative non-interactivity elsewhere in this
rem file) -- without this, any automation that sets those two flags but not HP_CI_LANE (a
rem documented, supported way to run this bootstrapper headlessly), or a contributor running a
rem full-bootstrap selfapps test locally (most do not pin HP_CI_LANE, see docs/agent-lessons-
rem learned.md "Accepted gap"), would hang on set /p with no console input available.
set "HP_CHECKPOINT_SITE=%~1"
echo.
echo *** Verification finished -- see the Run Status above. ***
echo *** You can run your program again now via the interpreter as an extra diagnostic check. (Optional) ***
set "HP_CHECKPOINT_RAW="
if defined HP_TEST_CHECKPOINT_ANSWER (
  set "HP_CHECKPOINT_RAW=%HP_TEST_CHECKPOINT_ANSWER%"
) else if defined HP_CI_LANE (
  set "HP_CHECKPOINT_RAW=n"
) else if defined NOINPUT (
  set "HP_CHECKPOINT_RAW=n"
) else if defined HP_NONINTERACTIVE (
  set "HP_CHECKPOINT_RAW=n"
) else (
  set /p "HP_CHECKPOINT_RAW=  Run again via the interpreter now? [Y/N] "
)
set "HP_CHECKPOINT_CHOICE=%HP_CHECKPOINT_RAW:~0,1%"
if /I not "%HP_CHECKPOINT_CHOICE%"=="Y" (
  call :log "[INFO] REQ-018: post-execution checkpoint (%HP_CHECKPOINT_SITE%): declined (run footprint stays at one execution)."
  set "HP_CHECKPOINT_SITE="
  set "HP_CHECKPOINT_RAW="
  set "HP_CHECKPOINT_CHOICE="
  exit /b 0
)
call :log "[INFO] REQ-018: post-execution checkpoint (%HP_CHECKPOINT_SITE%): accepted; running a second time via the interpreter."
if /I "%HP_CHECKPOINT_SITE%"=="exe" call :log "[INFO] REQ-018: note -- this diagnostic run uses the interpreter, not the packaged EXE, so behavior can differ (e.g. bundled resources)."
rem Reuses :run_failfast_probe (same never-kill, two-stage wait) rather than a fourth ad hoc
rem launch mechanism -- this elective run is exactly the same class of "user consciously
rem launched something that might run for a while" as the fail-fast probe's own interactive
rem branch, so it gets the same guarantees (never hard-killed, final outcome always reported).
rem Save/restore HP_SMOKE_RC and HP_PROBE_EXCEEDED around this second call: they belong to the
rem FIRST (real) verification run in the caller's namespace, and :run_failfast_probe would
rem otherwise overwrite both with the SECOND (elective) run's outcome, corrupting them for any
rem future code a caller might add after this checkpoint call before its own exit /b 0.
set "HP_CHECKPOINT_SAVED_SMOKE_RC=%HP_SMOKE_RC%"
set "HP_CHECKPOINT_SAVED_PROBE_EXCEEDED=%HP_PROBE_EXCEEDED%"
rem [REQ-026]: HP_PROBE_ARGS is a full, pre-quoted Windows Arguments string (entry path plus
rem any forwarded extra args), not a single bare path -- see :run_failfast_probe's own header
rem comment for the contract change this checkpoint's second run also relies on.
set "HP_PROBE_EXE=%HP_PY%"
set "HP_PROBE_ARGS="%HP_ENTRY%"%HP_APP_ARGS%"
set "HP_PROBE_CWD=%CD%"
rem Distinct output files from the primary run's ~run.out.txt/~run.err.txt: this is a genuinely
rem SECOND, separate execution, and reusing those paths would silently overwrite the FIRST run's
rem captured output out from under any downstream consumer (envsmoke's 'smoke-ok' token check,
rem the spaced-path dist\ token capture) that expects them to reflect the verified build.
set "HP_PROBE_OUT=~checkpoint_run.out.txt"
set "HP_PROBE_ERR=~checkpoint_run.err.txt"
call :run_failfast_probe checkpoint
set "HP_PROBE_OUT="
set "HP_PROBE_ERR="
set "HP_SMOKE_RC=%HP_CHECKPOINT_SAVED_SMOKE_RC%"
set "HP_PROBE_EXCEEDED=%HP_CHECKPOINT_SAVED_PROBE_EXCEEDED%"
set "HP_CHECKPOINT_SAVED_SMOKE_RC="
set "HP_CHECKPOINT_SAVED_PROBE_EXCEEDED="
set "HP_CHECKPOINT_SITE="
set "HP_CHECKPOINT_RAW="
set "HP_CHECKPOINT_CHOICE="
exit /b 0
:offer_optimized_build
rem AV-Safe Build Path requirement 9 (P1, docs/prd-av-safe-build-path.md): after a NORMAL,
rem verified-successful PyInstaller build (never after Tier A, which already produced a Nuitka
rem EXE -- see the HP_NUITKA_FALLBACK_USED guard below), offer an ELECTIVE, human-only,
rem auto-declined-in-CI upsell: build a second, Nuitka-optimized version and swap it into place
rem ONLY if it is confirmed to build AND run successfully. On any failure at any stage, the
rem original (already-verified) EXE is left completely untouched and the user is told their app
rem is still ready to use as-is -- this is a strictly safer sequence than Tier A's (which is
rem free to delete-then-rebuild because the original PyInstaller build already failed there).
rem
rem Mirrors :run_postexec_checkpoint's exact CI-safe consent-gate pattern one call site above
rem (auto-decline on HP_CI_LANE/NOINPUT/HP_NONINTERACTIVE, since like the checkpoint this fires
rem on essentially every successful bootstrap run) -- see that subroutine's own header comment
rem for why this broader 4-way auto-decline set is used here instead of the narrower 3-branch
rem gates elsewhere in this file (:system_build_consent_gate etc.).
if defined HP_NUITKA_FALLBACK_USED exit /b 0
if not "%HP_EXE_EXIT%"=="0" exit /b 0
echo.
echo *** Your app is ready. ***
echo *** Want to build an optimized version too? It takes a bit longer to build right now, ***
echo *** but it starts up more reliably on Windows and runs faster once it is built. (Optional, safe to skip) ***
set "HP_OPTBUILD_RAW="
if defined HP_TEST_OPTBUILD_ANSWER (
  set "HP_OPTBUILD_RAW=%HP_TEST_OPTBUILD_ANSWER%"
) else if defined HP_CI_LANE (
  set "HP_OPTBUILD_RAW=n"
) else if defined NOINPUT (
  set "HP_OPTBUILD_RAW=n"
) else if defined HP_NONINTERACTIVE (
  set "HP_OPTBUILD_RAW=n"
) else (
  set /p "HP_OPTBUILD_RAW=  Build the optimized version now? [Y/N] "
)
set "HP_OPTBUILD_CHOICE=%HP_OPTBUILD_RAW:~0,1%"
if /I not "%HP_OPTBUILD_CHOICE%"=="Y" (
  call :log "[INFO] Optimized build: declined."
  set "HP_OPTBUILD_RAW="
  set "HP_OPTBUILD_CHOICE="
  exit /b 0
)
set "HP_OPTBUILD_RAW="
set "HP_OPTBUILD_CHOICE="
call :log "[INFO] Optimized build: accepted; building now (this may take a minute or two)."
rem Build to a distinct temp filename first -- the already-working dist\%ENVNAME%.exe is never
rem touched until a build AND a verification run both succeed. HP_OPTBUILD_TMP is set before
rem the test-hook check below so :optbuild_cleanup can always reference it safely, including in
rem the forced-fail case where it names a file that was never created (a no-op exist check).
set "HP_OPTBUILD_TMP=%ENVNAME%.optimized_build.exe"
if exist "dist\%HP_OPTBUILD_TMP%" del "dist\%HP_OPTBUILD_TMP%" >nul 2>&1
if defined HP_TEST_FORCE_OPTBUILD_FAIL (
  call :log "[TEST] HP_TEST_FORCE_OPTBUILD_FAIL: simulating optimized-build failure."
  goto :optbuild_cleanup
)
if "%HP_ENV_MODE%"=="uv" (
  "%HP_UV_EXE%" pip install --python "%HP_PY%" -q nuitka >> "%LOG%" 2>&1
) else (
  "%HP_PY%" -m pip install -q nuitka >> "%LOG%" 2>&1
)
if errorlevel 1 (
  call :log "[WARN] Optimized build: could not install the build tool; your app is still ready to use as-is."
  goto :optbuild_cleanup
)
rem Same --assume-yes-for-downloads rationale as :try_nuitka_tier_a: Nuitka must never prompt
rem interactively here.
"%HP_PY%" -m nuitka --onefile --assume-yes-for-downloads --remove-output --output-dir=dist -o "%HP_OPTBUILD_TMP%" "%HP_ENTRY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  call :log "[WARN] Optimized build did not complete; your app is still ready to use as-is."
  call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help."
  goto :optbuild_cleanup
)
if not exist "dist\%HP_OPTBUILD_TMP%" (
  call :log "[WARN] Optimized build finished but did not produce output; your app is still ready to use as-is."
  goto :optbuild_cleanup
)
rem Verify the new build actually runs before touching the already-working original. Same
rem 30s-cap / Kill()-on-timeout pattern as :run_exe_smokerun -- this is a genuine internal
rem verification run (not the user's own session), so the same allowed-to-kill reasoning
rem applies (see HP_FAILFAST_PROBE_MS's header comment on the two classes of run in this file).
rem derived requirement: HP_APP_ARGS is read via $env:HP_APP_ARGS (PowerShell's own inherited-
rem environment access), never substituted by cmd.exe's %VAR% expansion into this -Command
rem string -- HP_APP_ARGS already contains literal embedded double-quotes per token (see its
rem definition near the top of this file), and cmd.exe's naive quote-toggle parser has no
rem concept of "this quote is inside a PowerShell string"; interpolating it directly would
rem corrupt this -Command argument exactly like the hazard docs/agent-lessons-learned.md
rem already documents for HP_FAILFAST_PROBE. Without this, a program that requires launch
rem arguments to start would always fail this verification, regardless of whether the
rem Nuitka build itself is fine.
set "HP_OPTBUILD_VERIFY_EXIT=-1"
rem CLAUDE.md Active Backlog Item 38: this internal verification of the elective Nuitka-optimized
rem build still runs from dist\ (current working directory (CWD) = dist), unlike
rem :run_exe_smokerun's own primary verification (now app root, see that subroutine's own
rem comment). Deliberately left as a documented, deferred follow-up rather than folded into this
rem slice -- no existing test depends on this specific CWD either way (selfapps_optimized_build.ps1's
rem stub app does no file I/O at all), and this is a narrower-blast-radius, opt-in-only code path,
rem not the widely-hit primary verification path Item 38 was actually about. Re-derive whether to
rem unify this too the next time this subroutine is touched.
pushd dist
for /f "usebackq delims=" %%X in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$si=New-Object System.Diagnostics.ProcessStartInfo;$si.FileName='%HP_OPTBUILD_TMP%';if($env:HP_APP_ARGS){$si.Arguments=$env:HP_APP_ARGS};$si.UseShellExecute=$false;$si.RedirectStandardOutput=$true;$si.RedirectStandardError=$true;$p=[System.Diagnostics.Process]::Start($si);$so=$p.StandardOutput.ReadToEndAsync();$se=$p.StandardError.ReadToEndAsync();$done=$p.WaitForExit(30000);if(-not $done){try{$p.Kill()}catch{}};$so.Result|Out-Null;$se.Result|Out-Null;if($done){$p.ExitCode}else{-1}"`) do set "HP_OPTBUILD_VERIFY_EXIT=%%X"
popd
if not defined HP_OPTBUILD_VERIFY_EXIT set "HP_OPTBUILD_VERIFY_EXIT=-1"
if not "%HP_OPTBUILD_VERIFY_EXIT%"=="0" (
  call :log "[WARN] Optimized build verification did not pass (exit %HP_OPTBUILD_VERIFY_EXIT%); your app is still ready to use as-is."
  goto :optbuild_cleanup
)
rem Verified good: swap it into place. A same-drive move is a fast rename on NTFS; /y overwrites
rem the existing (already-verified) dist\%ENVNAME%.exe.
if defined HP_TEST_FORCE_OPTBUILD_SWAP_FAIL (
  call :log "[TEST] HP_TEST_FORCE_OPTBUILD_SWAP_FAIL: simulating a failed swap (temp file deliberately left in place)."
) else (
  move /y "dist\%HP_OPTBUILD_TMP%" "dist\%ENVNAME%.exe" >nul 2>&1
)
rem A same-volume "move /y" onto an ALREADY-EXISTING destination is an atomic rename-replace:
rem on success the source is consumed (gone); on failure (e.g. an AV/indexer lock on the
rem destination -- the exact hazard class already documented for :try_embed_fallback's own
rem rd/move swap in docs/agent-lessons-learned.md) the whole operation is rejected and the
rem source is left untouched. Checking "does the destination exist" is NOT a valid success
rem proxy here (unlike embed's fresh-destination case): the destination is the already-working
rem original EXE, so it already exists before this line runs, success or failure alike. The
rem correct check is whether the SOURCE is now gone.
if exist "dist\%HP_OPTBUILD_TMP%" (
  call :log "[WARN] Optimized build verified successfully but could not be swapped into place; your app is still ready to use as-is."
  goto :optbuild_cleanup
)
set "HP_NUITKA_FALLBACK_USED=1"
call :log "[INFO] Optimized build succeeded and verified: dist\%ENVNAME%.exe now uses the fallback build system."
set "HP_OPTBUILD_TMP="
set "HP_OPTBUILD_VERIFY_EXIT="
exit /b 0
:optbuild_cleanup
if exist "dist\%HP_OPTBUILD_TMP%" del "dist\%HP_OPTBUILD_TMP%" >nul 2>&1
set "HP_OPTBUILD_TMP="
set "HP_OPTBUILD_VERIFY_EXIT="
exit /b 0
:try_fast_exe
set "HP_FASTPATH_USED="
set "HP_FAST_EXE="
set "HP_FAST_EXE_PATH="
if "%PYCOUNT%"=="0" exit /b 0
set "HP_FAST_EXE=dist\%ENVNAME%.exe"
if not exist "%HP_FAST_EXE%" exit /b 0
set "HP_FAST_EXE_PATH=%HP_FAST_EXE%"
set "HP_FASTPATH_TOKEN="
set "HP_FAST_CHECK_PS=~fast_check.ps1"
set "HP_FAST_CHECK_OUT=~fast_check.txt"
if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1
if exist "%HP_FAST_CHECK_OUT%" del "%HP_FAST_CHECK_OUT%" >nul 2>&1
call :emit_from_base64 "%HP_FAST_CHECK_PS%" HP_FAST_CHECK
if errorlevel 1 (
  call :log "[WARN] Fast path: could not emit ~fast_check.ps1; skipping fast path."
  exit /b 0
)
powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_FAST_CHECK_PS%" "%HP_FAST_EXE_PATH%" > "%HP_FAST_CHECK_OUT%" 2>> "%LOG%"
if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1
for /f "usebackq delims=" %%T in ("%HP_FAST_CHECK_OUT%") do set "HP_FASTPATH_TOKEN=%%T"
if exist "%HP_FAST_CHECK_OUT%" del "%HP_FAST_CHECK_OUT%" >nul 2>&1
if /I "%HP_FASTPATH_TOKEN%"=="fresh" (
  set "HP_FASTPATH_USED=1"
)
if not defined HP_FASTPATH_USED exit /b 0
rem REQ-012: super-user hook -- reuse the fresh cached EXE but do not run it.
if defined HP_SKIP_EXE_SMOKERUN (
  call :log "[INFO] REQ-012: HP_SKIP_EXE_SMOKERUN set; reusing cached EXE without running it (skipped by request)."
  set "HP_EXE_SKIPPED=1"
  exit /b 0
)
set "HP_PROBE_EXCEEDED="
rem Slice 2b-C: goto-based dispatch, NOT a parenthesized if/else block, is deliberate here --
rem see docs/agent-lessons-learned.md "Provider-cascade dispatch is goto-based on purpose".
rem cmd.exe expands every %VAR% in a parenthesized ( ... ) block ONCE, at parse time, using
rem values from BEFORE the block started -- an earlier revision of this code launched the EXE
rem and read "set HP_SMOKE_RC=%ERRORLEVEL%" inside the else-branch's own parens, which silently
rem froze %ERRORLEVEL% (and every in-block %HP_SMOKE_RC% read) to whatever it was right before
rem the if/else began (almost always "0"), so a genuinely broken cached EXE was NEVER discarded
rem in the legacy/CI branch. Each branch below is reached via goto so its statements are parsed
rem and executed as fresh top-level lines, exactly like the rest of this file's %ERRORLEVEL%
rem capture sites.
if defined HP_INTERACTIVE_RUN goto :try_fast_exe_probe
call :log "[INFO] Fast path: reusing %HP_FAST_EXE%"
>> "%LOG%" echo Fast path command: "%HP_FAST_EXE%" ^> "~run.out.txt" 2^> "~run.err.txt"
"%HP_FAST_EXE%"%HP_APP_ARGS% 1> "~run.out.txt" 2> "~run.err.txt"
set "HP_SMOKE_RC=%ERRORLEVEL%"
call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%"
if "%HP_SMOKE_RC%"=="0" (
  call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)"
) else (
  call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)"
)
goto :try_fast_exe_discard_check
:try_fast_exe_probe
rem Interactive fail-fast probe -- a real double-click user is told explicitly what is about
rem to launch and never sees a hard kill here; a genuinely stale/broken cached EXE that fails
rem within the probe window is still discarded+rebuilt below exactly as before. CI/automation
rem (HP_INTERACTIVE_RUN unset) takes the branch above instead, byte-for-byte unchanged, so
rem self.fastpath / self.exe.fastpath.graceful stay deterministic.
call :log "[INFO] Launching your program now via the cached standalone EXE (PyInstaller build): %HP_FAST_EXE%"
rem Resolve to an absolute path -- HP_FAST_EXE is relative (dist\%ENVNAME%.exe). .NET
rem Process.Start's FileName resolution is a different mechanism from cmd.exe's own relative
rem launch, so keep this unambiguous rather than relying on the child process inheriting the
rem right CWD to resolve it, in case a future change alters how/where this is invoked from.
set "HP_PROBE_EXE=%CD%\%HP_FAST_EXE%"
rem [REQ-026]: the EXE is self-contained (no separate entry-file argv needed), so Arguments
rem is exactly the forwarded extra args, or empty. HP_PROBE_ARGS is now a pre-quoted, ready
rem Windows Arguments string (see :run_failfast_probe's own header comment) -- HP_APP_ARGS is
rem already built that way by construction (each token individually re-quoted at capture time).
set "HP_PROBE_ARGS=%HP_APP_ARGS%"
set "HP_PROBE_CWD=%CD%"
call :run_failfast_probe fastpath
:try_fast_exe_discard_check
rem REQ-007: a reused EXE that exits non-zero must NOT abort the bootstrapper. The cached
rem EXE may be stale or carry an unbundled runtime dependency (DLL/data file) the fast-path
rem freshness check cannot see. Drop the fast path and fall through to a full rebuild, which
rem routes any persistent failure through :run_exe_smokerun's graceful handling + banner.
rem Slice 2b-C: this discard only fires when the probe was NOT exceeded (a fast, genuine
rem failure). Once a process is classified alive/healthy at the probe, a LATER non-zero exit
rem is presumed to be the user's own program outcome, not proof of a stale artifact -- a
rem rebuild would not fix a runtime bug in the user's own code, so the cached EXE is kept.
if not "%HP_SMOKE_RC%"=="0" if not defined HP_PROBE_EXCEEDED (
  call :log "[WARN] Fast path standalone EXE (PyInstaller build) exited %HP_SMOKE_RC%; discarding cached EXE and rebuilding."
  rem Delete the broken EXE so the next :try_fast_exe call does not re-detect it as
  rem "fresh" and run the known-bad binary again; the rebuild below recreates it.
  if exist "%HP_FAST_EXE%" del "%HP_FAST_EXE%" >nul 2>&1
  set "HP_FASTPATH_USED="
  set "HP_SMOKE_RC="
)
exit /b 0
:write_fast_hash
rem derived requirement (CLAUDE.md Active Backlog Item 39): called only when the caller's own
rem HP_FRESH_BUILD_OK gate confirms a genuine build succeeded THIS run (see :run_entry_smoke's
rem reset + 4 success-branch sets) -- never for a fast-path reuse (nothing changed, stored hash
rem already correct) and never for a skipped/failed rebuild (would pair the CURRENT sources'
rem hash with a stale leftover EXE from an earlier run -- a real bug CodeRabbit's review caught
rem on PR #460 in an earlier, less precise HP_FASTPATH_USED-only gate). (Re)writes the
rem composite source-content hash the EXE fast path's freshness check compares against on the
rem next run. A dedicated subroutine, not inlined at :success, specifically so no line here
rem sits inside a parenthesized block -- see docs/agent-lessons-learned.md's "Parse-time
rem vs. runtime variable expansion" entry for why a set-then-read-in-the-same-block
rem pattern is unsafe, and this repo's own house preference for goto/call-based dispatch
rem over inlined blocks wherever a payload emit + invoke sequence is involved.
if not exist "dist\%ENVNAME%.exe" exit /b 0
set "HP_FAST_CHECK_PS=~fast_check.ps1"
if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1
call :emit_from_base64 "%HP_FAST_CHECK_PS%" HP_FAST_CHECK
if errorlevel 1 exit /b 0
powershell -NoProfile -ExecutionPolicy Bypass -File "%HP_FAST_CHECK_PS%" "dist\%ENVNAME%.exe" write >> "%LOG%" 2>&1
set "HP_FAST_HASH_RC=%ERRORLEVEL%"
rem derived requirement (CodeRabbit review, PR #460): a write failure here is not fatal -- the
rem next run's missing-hash default safely forces exactly one rebuild -- but a silent failure
rem could repeat unnoticed across many runs. Log it explicitly rather than swallowing it.
if not "%HP_FAST_HASH_RC%"=="0" call :log "[WARN] Fast-path hash write failed; the next run will rebuild."
if exist "%HP_FAST_CHECK_PS%" del "%HP_FAST_CHECK_PS%" >nul 2>&1
set "HP_FAST_HASH_RC="
exit /b 0
:run_entry_smoke
call :record_chosen_entry "%HP_ENTRY%"
rem CLAUDE.md Active Backlog Item 45: a failed env-create can fall through :die (exit /b
rem returns from the call frame, it does not halt the process -- see docs/agent-lessons-
rem learned.md's ":die uses exit /b" entry) with HP_PY left pointing at a python.exe that
rem was never produced. :after_env_mode_selection's own interpreter smoke test already sets
rem HP_NO_INTERPRETER for this case today, but that protection is 800+ lines upstream and
rem indirect; this is a direct, explicit backstop at the actual point of use, so the
rem build/warnfix/repair block below can never be reached against a nonexistent HP_PY even
rem if some future change removes or bypasses the upstream smoke test. Setting
rem HP_NO_INTERPRETER here (instead of a bespoke message) also fixes a latent message-framing
rem bug: without it, a broken HP_PY would instead fail :preflight_compile's own py_compile
rem call and get misreported as a syntax error in the user's code, not a missing interpreter.
if not exist "%HP_PY%" set "HP_NO_INTERPRETER=1"
rem REQ-021: static pre-flight syntax check of the entry (no user code executed). A SyntaxError
rem makes the program unrunnable under the interpreter AND unbuildable by PyInstaller, so report it
rem clearly and stop here instead of failing later inside the doomed PyInstaller build.
call :preflight_compile
if defined HP_PREFLIGHT_FAILED (
  set "HP_BOOTSTRAP_STATE=error"
  exit /b 0
)
set "HP_FASTPATH_USED="
set "HP_SMOKE_RC="
rem REQ-012: super-user hook -- skip the entry-script smoke test (and fast-path EXE
rem reuse, which also executes the program) so no user code runs. Env creation,
rem dependency install, and the PyInstaller build still proceed; the result is left
rem unverified (HP_SMOKE_RC stays empty), not a fake pass or fail.
if defined HP_SKIP_ENTRY_SMOKE (
  call :log "[INFO] REQ-012: HP_SKIP_ENTRY_SMOKE set; skipping entry-script smoke test (no user code executed)."
  goto :run_entry_after_smoke
)
call :try_fast_exe
rem REQ-018 (2b-A.2): single verification -- the redundant pre-build interpreter smoke is removed.
rem In the EXE path the timed EXE smoke (:run_exe_smokerun) is now the sole verification run, so the
rem app is no longer executed twice (interpreter then EXE). When no EXE is produced (system-Python
rem decline / build skipped), the interpreter runs ONCE via :verify_no_exe_interpreter after the
rem build gate. The fast-path EXE run inside :try_fast_exe is the user's run and is left as-is here
rem (its run-timing/consent is unified with the no-EXE interpreter in slice 2b-C).
:run_entry_after_smoke
rem derived requirement: the CI harness inspects the breadcrumb log to flag missing entries.
set "HP_BREADCRUMB=~entry1_bootstrap.log"
if exist "tests\~entry1\" set "HP_BREADCRUMB=tests\~entry1\~entry1_bootstrap.log"
if not exist "%HP_BREADCRUMB%" (
  rem derived requirement: create the breadcrumb when the smoke run succeeds so diagnostics stay consistent.
  for %%B in ("%HP_BREADCRUMB%") do if not "%%~dpB"=="" if not exist "%%~dpB" mkdir "%%~dpB" >nul 2>&1
  type nul > "%HP_BREADCRUMB%"
)
if exist "%HP_BREADCRUMB%" (
  call :log "[INFO] Entry smoke breadcrumb exists: %HP_BREADCRUMB%"
) else (
  call :log "[WARN] Entry smoke missing breadcrumb: %HP_BREADCRUMB%"
)
rem derived requirement (REQ-007 / Windows Server 2025 + VS2026 image): on conda-forge
rem Python, pyexpat.pyd is dynamically linked against a separate libexpat-*.dll in the
rem env's Library\bin. PyInstaller bundles pyexpat.pyd but not that DLL, so the frozen EXE
rem hits "DLL load failed while importing pyexpat" whenever a stdlib XML path runs (e.g.
rem openpyxl -> xml.etree.ElementTree). Bundle the DLL explicitly via --add-binary.
rem Stock python.org builds statically link expat, and venv/uv layouts have no Library\bin,
rem so this detection finds nothing and HP_PYI_EXPAT stays empty (no behavior change).
set "HP_PYI_EXPAT="
set "HP_EXPAT_DLL="
for %%I in ("%HP_PY%") do set "HP_PY_DIR=%%~dpI"
if defined HP_PY_DIR for /f "delims=" %%D in ('dir /b /a-d "%HP_PY_DIR%Library\bin\libexpat*.dll" 2^>nul') do if not defined HP_EXPAT_DLL set "HP_EXPAT_DLL=%HP_PY_DIR%Library\bin\%%D"
if defined HP_EXPAT_DLL (
  call :log "[INFO] REQ-007: bundling conda libexpat DLL for pyexpat: %HP_EXPAT_DLL%"
  set "HP_PYI_EXPAT=--add-binary "%HP_EXPAT_DLL%;.""
)
rem REQ-005.x: pre-build --collect-submodules for curated packages that load
rem submodules dynamically (the warn file is silent about them, so warnfix never
rem repairs them). Double-gated inside the helper (used-by-source AND installed) so
rem a fat global env never bloats a lean app EXE. Computed in a subroutine so
rem %HP_PYI_COLLECT% resolves at parse time inside the build block below.
call :compute_collect_flags
rem REQ-007: provider-independent build. The EXE build is attempted under every provider. System
rem Python (Tier 4) is the one exception: building installs PyInstaller into the user's system
rem interpreter, so it is gated on explicit consent (CI auto-declines); all other providers build.
set "HP_BUILD_OK=1"
if /i "%HP_ENV_MODE%"=="system" call :system_build_consent_gate
if /i "%HP_ENV_MODE%"=="system" if errorlevel 1 set "HP_BUILD_OK="
rem Slice 2b-C: recompute the same HP_FASTPATH_RUN_FAILED check the top-of-file gate uses --
rem this is :try_fast_exe's SECOND call site, inside :run_entry_smoke; the first call's own
rem HP_FASTPATH_RUN_FAILED does not reach here -- any first-call success or post-probe-failure
rem outcome already took goto :success before this point, so this recomputation is normally a
rem no-op today, but keeps this call site from silently reopening the same "HP_FASTPATH_USED
rem alone is not proof of a clean run" gap the top-of-file gate closed, should a future change,
rem e.g. a provider-cascade re-entry, ever reach here with HP_FASTPATH_USED still set from a
rem probe-classified alive-then-failed run. Computed as a top-level statement, not inside the
rem block below, for the same parse-time-expansion reason documented in :try_fast_exe.
set "HP_FASTPATH_RUN_FAILED="
if defined HP_SMOKE_RC if not "%HP_SMOKE_RC%"=="0" set "HP_FASTPATH_RUN_FAILED=1"
if not defined HP_BUILD_OK (
  call :log "[INFO] REQ-007: system-Python EXE build not consented; skipping PyInstaller packaging. The environment and dependencies are installed; run the app directly via the prepared Python."
) else (
  if defined HP_FASTPATH_USED (
    if defined HP_FASTPATH_RUN_FAILED (
      call :log "[WARN] dist\%ENVNAME%.exe (standalone EXE, PyInstaller build) ran to completion and exited non-zero after passing the fail-fast probe; treated as your program's own result, not a rebuild trigger."
    ) else (
      call :log "[INFO] Fast path: skipping PyInstaller rebuild for existing dist\%ENVNAME%.exe"
    )
  ) else (
    rem derived requirement: PyInstaller install + build can take a minute or more; emit a
    rem user-facing message before the silent operation so the script never looks hung.
    call :log "[INFO] Building standalone executable -- this may take a minute or two..."
    rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: snapshot
    rem %LOG%'s size before this fresh build, and any warnfix rebuild that may follow it,
    rem writes anything, so :dll_bundle_recover can later scan only THIS run's own build
    rem output for an unresolved-native-DLL warning, never a stale one left over from an
    rem earlier run in the same persistent log file.
    for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ"
    rem derived requirement: on many machines, a benign one-line Windows message about a
    rem missing drive prints here from an unrelated background process around this same
    rem window, confirmed harmless and already tolerated by tests/selfapps_envsmoke.ps1's
    rem unexpectedSystemErrorIgnored allowlist -- not from this build command itself, whose
    rem own stdout/stderr are fully redirected to %LOG% below. A beginner watching the console
    rem could mistake it for a real error, so name it explicitly -- worded to avoid literally
    rem reproducing the trigger phrase, so this reassurance line itself is never mistaken by
    rem selfapps_envsmoke.ps1's Get-LineSnippet substring search for the real system line it
    rem allowlists -- which would misfire on the FIRST matching line, not necessarily this one.
    call :log "[INFO] (A stray one-line Windows message about a missing drive may appear next -- that is a known side effect from an unrelated background process, unrelated to your app; safe to ignore.)"
    :: derived requirement: ~parse_warn.py was written against PyInstaller 5.x and 6.x warn-file formats.
    :: Version is intentionally unpinned so future PyInstaller releases are adopted automatically.
    :: If CI starts failing parse_warn tests after a PyInstaller update, review ~parse_warn.py
    :: against the new warn-file format and update the translation table as needed.
    if "%HP_ENV_MODE%"=="uv" (
      "%HP_UV_EXE%" pip install --python "%HP_PY%" -q pyinstaller >> "%LOG%" 2>&1
    ) else (
      "%HP_PY%" -m pip install -q pyinstaller >> "%LOG%" 2>&1
    )
    if exist "%ENVNAME%.spec" set "HP_SPEC_PREEXIST=1"
    rem derived requirement: a real bug, found 2026-07-20 while scoping the AV-Safe Build Path
    rem PRD's failure-simulation tests, docs/prd-av-safe-build-path.md requirement 1: :die only
    rem returns from its own `call` frame -- see docs/agent-lessons-learned.md's ":die uses exit
    rem /b" note -- it does NOT halt the process, and nothing downstream re-checked
    rem dist\%ENVNAME%.exe or this call's outcome. Without HP_BOOTSTRAP_STATE=error, a genuine
    rem PyInstaller build failure fell through to :run_exe_smokerun, a silent no-op skip when
    rem the EXE is missing, and :verify_no_exe_interpreter, which runs the raw entry via the
    rem interpreter instead, then :after_cascade_decision unconditionally overwrote ~bootstrap.status.json
    rem back to state=ok and the process exited 0 -- silently masking the failed EXE build the
    rem user explicitly consented to, HP_BUILD_OK. Mirrors the existing, already-correct
    rem HP_BOOTSTRAP_STATE=error precedent in :run_entry_smoke's preflight-failure branch.
    rem Nested if/else, no goto, is used deliberately: a goto that jumps to a label inside this
    rem same parenthesized else-block risks the class of paren-tracking corruption documented
    rem under "Provider-cascade dispatch is goto-based on purpose" -- this block instead stays
    rem entirely within ordinary if/else nesting, using only call/if-errorlevel/if-defined/set,
    rem all of which are already-confirmed runtime-safe inside a parenthesized block.
    rem AV-Safe Build Path requirements 2-4, Tier A: each of the three failure points below --
    rem forced-fail test hook, real build errorlevel, missing/vanished output, requirement 3's
    rem single trigger category -- now attempts :try_nuitka_tier_a before declaring final failure,
    rem instead of going straight to :die. On Tier A success, HP_NUITKA_FALLBACK_USED=1 and
    rem dist\%ENVNAME%.exe exists, built by Nuitka; the rest of this block treats it exactly
    rem like a PyInstaller-produced EXE -- parse_warn/warnfix below is NOT guaranteed to be a
    rem no-op for it: a stale warn-%ENVNAME%.txt from the earlier, failed PyInstaller attempt
    rem that triggered this Tier A fallback can survive, since build\%ENVNAME% is not cleared
    rem before this check -- warnfix can still fire and even rebuild over the Nuitka-built EXE, see
    rem the HP_NUITKA_FALLBACK_USED clear a few dozen lines below for how that case is handled.
    set "HP_NUITKA_FALLBACK_USED="
    rem CLAUDE.md Active Backlog Item 39, CodeRabbit review, PR #460: a real correctness bug --
    rem gating :write_fast_hash on "HP_FASTPATH_USED unset" alone does not prove a build actually
    rem SUCCEEDED this run; a stale dist\%ENVNAME%.exe left over from an EARLIER successful run
    rem would still be sitting there if THIS run's rebuild is skipped/fails, and the freshness
    rem hash would get rewritten for the CURRENT, changed, sources paired with that OLD binary
    rem -- the next run would then wrongly trust the stale EXE as "fresh." HP_FRESH_BUILD_OK is
    rem set ONLY in the genuine-success branches below -- PyInstaller producing
    rem dist\%ENVNAME%.exe, or Tier A/Nuitka succeeding after it didn't -- never on a
    rem warn_build_incomplete path.
    rem Reset once per fresh build attempt, same reasoning as HP_NUITKA_FALLBACK_USED above.
    set "HP_FRESH_BUILD_OK="
    rem REQ-009/REQ-005.10, cascade-vs-postexec fix: reset the "this provider's dependencies
    rem look incomplete" flag at the start of every fresh build attempt, not just when warnfix
    rem happens to run again -- a provider that needs no warnfix repair at all must not inherit
    rem a stale flag left by an earlier, cascaded-away provider's failure. Set if warranted, and
    rem consumed further down; see :warnfix_cascade_detect and :warn_user_code_launch.
    set "HP_DEP_MAYBE_INCOMPLETE="
    rem CLAUDE.md Item 29: :dll_bundle_recover can now be called MORE THAN ONCE per fresh build
    rem attempt -- a second pass after :hidden_import_recover, see that call site below -- so its
    rem own accumulated --add-binary flags, HP_PYI_DLLBIND, must survive across those calls
    rem rather than being wiped at the start of each one. Reset once here instead, at the start
    rem of the fresh build attempt itself -- the same "not just when X happens to run again"
    rem reasoning as HP_DEP_MAYBE_INCOMPLETE above, so a cascaded-away provider's own bindings
    rem never leak into the next tier's build. HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT need the
    rem same reset for the mirror-image reason: :dll_bundle_recover's OWN rebuild command now
    rem threads them through too, see its own comment, so a stale value from a PREVIOUS
    rem provider's :hidden_import_recover call must not leak into THIS provider's first
    rem :dll_bundle_recover call, before THIS provider's own :hidden_import_recover has run.
    set "HP_PYI_DLLBIND="
    set "HP_PYI_HIDDEN_IMPORTS="
    set "HP_PYI_HID_COLLECT="
    rem docs/open-questions.md item 1, answered yes: the post-flight caveat panel's DLL-specific
    rem hint reads HP_DLL_HINT_STATE, set by :emit_dll_bundle_row on every call and otherwise left
    rem alone -- NOT reset at :dll_bundle_recover_exit, unlike HP_DLL_FAILED/HP_DLL_EXHAUSTED, so it
    rem survives that subroutine returning. Reset it here, once per fresh build attempt, for the
    rem same reason as HP_DEP_MAYBE_INCOMPLETE above -- a provider tier this run's own
    rem :dll_bundle_recover never even calls, e.g. this attempt is not conda, or no DLL warning
    rem appears at all, must not inherit a stale hint left by an earlier, cascaded-away provider.
    set "HP_DLL_HINT_STATE="
    rem CLAUDE.md Active Backlog Item 41: the post-flight caveat panel's GUI-app-aware hint reads
    rem HP_EXE_TIMEDOUT_SILENT, set below where HP_EXE_VERIFY_FAILED is set. Reset here for the
    rem identical reason as HP_DLL_HINT_STATE above -- a cascaded-away provider's own timed-out
    rem verification must not leak a stale hint into a later provider tier's own caveat panel.
    set "HP_EXE_TIMEDOUT_SILENT="
    if defined HP_TEST_FORCE_PYINSTALLER_FAIL (
      call :log "[TEST] HP_TEST_FORCE_PYINSTALLER_FAIL: simulating PyInstaller build failure."
      call :try_nuitka_tier_a
      if errorlevel 1 (
        rem CLAUDE.md Active Backlog Item 46, Bucket B: every build tool -- PyInstaller AND the
        rem Nuitka fallback -- has already failed by this point, but the environment/dependencies
        rem are still valid and the interpreter-fallback verification a few hundred lines below
        rem still genuinely runs and still genuinely decides success/failure -- this is not a
        rem doomed state, so :die's own mid-run pause, which would stop the user here before
        rem that verification even happens, and premature lock release are both wrong. Warn and
        rem keep going instead; :warn_build_incomplete still sets HP_BOOTSTRAP_STATE=error so the
        rem final ~bootstrap.status.json/postflight panel report this honestly once the run
        rem actually finishes.
        call :warn_build_incomplete "[WARN] PyInstaller execution failed; will verify your code directly via Python instead. reason=test_forced_fail"
      ) else (
        set "HP_NUITKA_FALLBACK_USED=1"
        set "HP_FRESH_BUILD_OK=1"
      )
    ) else (
      "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1
      if errorlevel 1 (
        call :try_nuitka_tier_a
        if errorlevel 1 (
          rem CLAUDE.md Item 33: reason=build_error means PyInstaller's own process exited
          rem nonzero -- distinct from reason=missing_output below, exit 0 but no EXE, and
          rem reason=test_forced_fail above, no real build ever ran. Mirrors the existing
          rem UV_FALLBACK reason= token convention; see docs/agent-lessons-learned.md.
          rem CLAUDE.md Active Backlog Item 46, Bucket B: see the test_forced_fail branch above
          rem for why this is a warn-and-continue site, not a :die site.
          call :warn_build_incomplete "[WARN] PyInstaller execution failed; will verify your code directly via Python instead. reason=build_error"
        ) else (
          set "HP_NUITKA_FALLBACK_USED=1"
          set "HP_FRESH_BUILD_OK=1"
        )
      ) else (
        if defined HP_TEST_FORCE_OUTPUT_VANISH if exist "dist\%ENVNAME%.exe" (
          call :log "[TEST] HP_TEST_FORCE_OUTPUT_VANISH: deleting freshly-built EXE to simulate post-creation removal."
          del "dist\%ENVNAME%.exe" >nul 2>&1
        )
        if not exist "dist\%ENVNAME%.exe" (
          call :try_nuitka_tier_a
          if errorlevel 1 (
            rem CLAUDE.md Active Backlog Item 46, Bucket B: see the test_forced_fail branch
            rem above for why this is a warn-and-continue site, not a :die site.
            call :warn_build_incomplete "[WARN] PyInstaller did not produce dist\%ENVNAME%.exe; will verify your code directly via Python instead. reason=missing_output"
          ) else (
            set "HP_NUITKA_FALLBACK_USED=1"
            set "HP_FRESH_BUILD_OK=1"
          )
        ) else (
          call :log "[INFO] PyInstaller produced dist\%ENVNAME%.exe"
          set "HP_FRESH_BUILD_OK=1"
        )
      )
    )
    rem parse_warn: check PyInstaller warn file for missing modules before cleanup
    rem derived requirement: build\ must still exist when ~parse_warn.py runs.
    rem derived requirement: use %ENVNAME%, set before this block, as the inline
    rem path, not a variable set inside the same else-block; cmd.exe expands
    rem %VAR% at parse time for the whole block, so HP_WARNFILE would be empty.
    if exist "build\%ENVNAME%\warn-%ENVNAME%.txt" (
      call :log "[DEBUG] warnfix: warn file found"
      type "build\%ENVNAME%\warn-%ENVNAME%.txt" >> "%LOG%"
      copy "build\%ENVNAME%\warn-%ENVNAME%.txt" "~warnfile.txt" >nul 2>&1
      rem derived requirement: the raw warn-file dump right above this line goes only to
      rem ~setup.log -- type ... >> "%LOG%" has no console echo -- so "the list above" was
      rem misleading on the console -- a user watching only the window never saw a list.
      rem Point at ~warnfile.txt, already copied next to the app a few lines up, instead.
      call :log "[INFO] warnfix: some modules could not be automatically bundled (full list in ~warnfile.txt / ~setup.log); modules such as posix, fcntl, grp, pwd, resource, _scproxy, _posixsubprocess, collections.abc, and _frozen_importlib_external are expected on Windows and are filtered out automatically; cStringIO and StringIO (Python-2-only compatibility shims some packages still reference) are filtered out automatically too."
      if defined HP_NDJSON (
        powershell -NoProfile -ExecutionPolicy Bypass -Command ^
          "$row = @{ id='self.warnfix.platform_filter'; pass=$true; detail='posix_modules_expected_on_windows' } | ConvertTo-Json -Compress -Depth 8;" ^
          "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
      )
    ) else (
      call :log "[DEBUG] warnfix: warn file not found"
    )
    if exist "~missing_modules.txt" del "~missing_modules.txt" >nul 2>&1
    call :emit_from_base64 "~parse_warn.py" HP_PARSE_WARN
    "%HP_PY%" ~parse_warn.py "%ENVNAME%" > "~missing_modules.txt" 2>> "%LOG%"
    if exist "~parse_warn.py" del "~parse_warn.py" >nul 2>&1
    set "HP_WARNFIX_NEEDED="
    for /f "usebackq delims=" %%M in ("~missing_modules.txt") do set "HP_WARNFIX_NEEDED=1"
    if exist "~warnfix_repair_failed.flag" del "~warnfix_repair_failed.flag" >nul 2>&1
    if defined HP_WARNFIX_NEEDED (
      set "HP_WARNFIX_APPLIED=1"
      call :log "[REPAIR] missing modules detected; installing and rebuilding."
      if "%HP_ENV_MODE%"=="uv" (
        for /f "usebackq delims=" %%M in ("~missing_modules.txt") do (
          call :log "[INFO] Attempting to install: %%M"
          "%HP_UV_EXE%" pip install --python "%HP_PY%" %%M >> "%LOG%" 2>&1
          if errorlevel 1 (
            call :log "[WARN] Repair failed: %%M"
            copy nul "~warnfix_repair_failed.flag" >nul 2>&1
          ) else (
            call :log "[INFO] Installed: %%M"
          )
        )
      ) else if "%HP_ENV_MODE%"=="conda" if defined CONDA_BAT (
        rem derived requirement, CodeRabbit finding on Item 36's own PR: :select_conda_bat
        rem sets CONDA_BAT purely from Miniconda binary presence on disk, never clears it once
        rem set -- so a genuine venv/embed fallback, conda env CREATE failed, cascaded past conda,
        rem can still leave CONDA_BAT defined if Miniconda itself was already on disk.
        rem Gating on HP_ENV_MODE=="conda" too, not just "defined CONDA_BAT", stops that stale
        rem definedness from routing a real venv/embed repair into a conda-install command
        rem targeting an environment that was never created.
        for /f "usebackq delims=" %%M in ("~missing_modules.txt") do (
          call :log "[INFO] Attempting to install: %%M"
          call "%CONDA_BAT%" install -y -n "%ENVNAME%" --override-channels -c conda-forge %%M >> "%LOG%" 2>&1
          if errorlevel 1 (
            call :log "[WARN] Repair failed: %%M"
            copy nul "~warnfix_repair_failed.flag" >nul 2>&1
          ) else (
            call :log "[INFO] Installed: %%M"
          )
        )
      ) else if "%HP_ENV_MODE%"=="venv" (
        rem derived requirement, CLAUDE.md Item 36: the repair-install dispatch above only had
        rem uv/conda branches, silently no-opping under venv/embed/system while still logging
        rem "installing and rebuilding" and "rebuild complete" as if it worked -- both a private,
        rem bootstrapper-owned interpreter -- venv/embed, same as the MAIN non-repair
        rem dependency-install dispatch a few hundred lines above -- already have a working pip;
        rem this just wires that existing capability into the second call site that was missed.
        for /f "usebackq delims=" %%M in ("~missing_modules.txt") do (
          call :log "[INFO] Attempting to install: %%M"
          "%HP_PY%" -m pip install %%M >> "%LOG%" 2>&1
          if errorlevel 1 (
            call :log "[WARN] Repair failed: %%M"
            copy nul "~warnfix_repair_failed.flag" >nul 2>&1
          ) else (
            call :log "[INFO] Installed: %%M"
          )
        )
      ) else if "%HP_ENV_MODE%"=="embed" (
        for /f "usebackq delims=" %%M in ("~missing_modules.txt") do (
          call :log "[INFO] Attempting to install: %%M"
          "%HP_PY%" -m pip install %%M >> "%LOG%" 2>&1
          if errorlevel 1 (
            call :log "[WARN] Repair failed: %%M"
            copy nul "~warnfix_repair_failed.flag" >nul 2>&1
          ) else (
            call :log "[INFO] Installed: %%M"
          )
        )
      ) else (
        rem system mode deliberately stays a no-op here too, matching the MAIN dependency-install
        rem dispatch's own "System fallback: skipping requirement installation." convention --
        rem system is a shared, uncontrolled interpreter; REQ-009 avoids installing into it.
        call :log "[WARN] System fallback: skipping warnfix repair installation."
      )
      if exist "~warnfix_repair_failed.flag" call :log "[WARN] One or more repair attempts failed"
      call :log "[INFO] Rebuilding standalone executable after warnfix -- this may take a minute or two..."
      "%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1
      rem derived requirement, bug-hunt pass: unlike the ORIGINAL build a few dozen lines
      rem above, which routes every failure through :try_nuitka_tier_a / :warn_build_incomplete /
      rem HP_BOOTSTRAP_STATE=error, this warnfix-triggered rebuild previously had NO failure
      rem handling at all -- the log line below always said "rebuild complete" and nothing
      rem re-checked dist\%ENVNAME%.exe, so a genuine rebuild failure -- e.g. the exact AV-lock
      rem class the whole AV-Safe Build Path PRD exists to route around -- fell through to
      rem :run_exe_smokerun's silent no-op-when-missing skip, then a clean interpreter-fallback
      rem run, ending in a false ~bootstrap.status.json state=ok. Deliberately NOT retried via
      rem :try_nuitka_tier_a here, unlike the original build -- this rebuild only exists to
      rem bundle a module warnfix already installed into an EXE that was already confirmed
      rem working before this rebuild attempt; the conservative, honest response to a failure
      rem is to report it, not to speculatively rebuild via a second tool inside an
      rem already-nested failure path.
      if errorlevel 1 (
        call :log "[ERROR] PyInstaller execution failed during warnfix rebuild; the previous build may no longer be valid."
        set "HP_BOOTSTRAP_STATE=error"
        rem CodeRabbit review, PR #464: this branch never cleared HP_FRESH_BUILD_OK, so
        rem :write_fast_hash would still pair the CURRENT sources with whatever stale,
        rem warnfix-incomplete EXE is left in dist\ from before this failed rebuild -- the
        rem next run's fast path would then wrongly trust it as fresh and skip retrying the
        rem repair. Mirrors the identical PR #460 fix for the ORIGINAL build's own failure
        rem branches; unlike a DLL-bundle/hidden-import repair loop failure -- bundling-only,
        rem does not need this per docs/agent-interconnect.md -- a failed warnfix rebuild means
        rem the current dist\ EXE genuinely lacks a needed dependency.
        set "HP_FRESH_BUILD_OK="
      ) else if not exist "dist\%ENVNAME%.exe" (
        call :log "[ERROR] PyInstaller did not produce dist\%ENVNAME%.exe during warnfix rebuild."
        set "HP_BOOTSTRAP_STATE=error"
        set "HP_FRESH_BUILD_OK="
      ) else (
        call :log "[REPAIR] rebuild complete after warnfix."
        rem The warnfix rebuild always uses PyInstaller -- if the EXE it just replaced was
        rem previously Nuitka-built, Tier A, it no longer is; clear the flag so
        rem :hidden_import_recover's Nuitka-skip guard doesn't wrongly skip repair on what is
        rem now genuinely a PyInstaller-built EXE -- see docs/agent-interconnect.md's "Tier A and
        rem hidden-import auto-recovery" section.
        set "HP_NUITKA_FALLBACK_USED="
      )
      rem REQ-005.11: warnfix-trigger PEP 723 write-back. Must run here, not later --
      rem ~missing_modules.txt and ~warnfix_repair_failed.flag are both still on disk at
      rem this point and are deleted shortly after, see below.
      call :pep723_writeback warnfix
      rem REQ-009/REQ-005.10, slice 1: detect only: flag when this provider could not
      rem resolve all modules. Must run before the repair-failed flag is deleted, next line.
      call :warnfix_cascade_detect
    )
    if exist "~warnfix_repair_failed.flag" del "~warnfix_repair_failed.flag" >nul 2>&1
    if exist "~missing_modules.txt" del "~missing_modules.txt" >nul 2>&1
    set "HP_WARNFIX_NEEDED="
    if not defined HP_SPEC_PREEXIST if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1
    set "HP_SPEC_PREEXIST="
    if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1
    call :log "[INFO] PyInstaller build artifacts cleaned up."
    rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: build-time
    rem native-DLL bundling repair -- Requirement 2's chosen design: react to PyInstaller's own
    rem build-log warning here, before the smoke run below, rather than waiting for the
    rem guaranteed runtime DLL-load-failure crash a missing native dependency would otherwise
    rem produce. Must run before :run_exe_smokerun so a repaired EXE is what actually gets
    rem verified. See docs/agent-interconnect.md's "Conda native-DLL bundling repair loop" for
    rem the full mechanism and its relationship to :hidden_import_recover below.
    call :dll_bundle_recover
    rem REQ-009/REQ-005.10, cascade-vs-postexec fix: the smoke run itself is NOT skipped here,
    rem even when HP_CASCADE_APPROVED is set -- approval only means the NEXT provider tier will
    rem be TRIED; :provider_cascade, reached later from the top-level main line once this whole
    rem subroutine returns, can still find every remaining tier unavailable/declined and fall
    rem back to "keeping current build" -- the build this smoke run is about to verify. Skipping
    rem it here would leave that kept build completely unverified in the exhaustion case. Only
    rem the two ELECTIVE follow-up offers, postexec checkpoint and optimized build, are suppressed,
    rem inside :smokerun_ndjson below -- see that label's own comment for why that scope is safe.
    call :run_exe_smokerun
  )
)
call :verify_no_exe_interpreter
set "HP_WARNFIX_APPLIED="
set "HP_FAST_EXE="
set "HP_FAST_EXE_PATH="
set "HP_FASTPATH_USED="
set "HP_FASTPATH_TOKEN="
set "HP_PYI_EXPAT="
set "HP_EXPAT_DLL="
set "HP_PY_DIR="
set "HP_LOG_SIZE_BEFORE="
set "HP_PYI_DLLBIND="
exit /b 0
:preflight_compile
rem REQ-021: static pre-flight -- byte-compile the entry to catch a SyntaxError in the user's own
rem code early and clearly, before the doomed PyInstaller build. py_compile uses the same parser as
rem the interpreter (zero false positives for the entry) and writes NO .pyc on failure. No setlocal:
rem HP_PREFLIGHT_FAILED must persist to the caller. Capture %ERRORLEVEL% immediately (the del/set
rem below would clobber it).
set "HP_PREFLIGHT_FAILED="
if defined HP_NO_INTERPRETER (
  echo.
  echo *** [ERROR] No Python interpreter is available; your program was not run or built. ***
  echo *** This is not a syntax error -- the Python interpreter itself could not be used. ***
  echo *** Either every automatic Python-acquisition method -- uv, conda, a fresh download, ***
  echo *** or a local virtual environment -- failed, usually from no internet connection, a ***
  echo *** full disk, or a locked-down managed machine image -- or a PVW_PYTHON_EXE override ***
  echo *** points at a path that does not run. Scroll up in this window for the specific reason. ***
  call :log "[ERROR] REQ-021: preflight skipped, no Python interpreter resolved: %HP_ENTRY%"
  echo.
  set "HP_PREFLIGHT_FAILED=1"
  exit /b 0
)
if not defined HP_ENTRY exit /b 0
if not exist "%HP_ENTRY%" exit /b 0
if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1
"%HP_PY%" -m py_compile "%HP_ENTRY%" 2> "~preflight.err.txt"
set "HP_PREFLIGHT_RC=%ERRORLEVEL%"
if "%HP_PREFLIGHT_RC%"=="0" (
  if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1
  set "HP_PREFLIGHT_RC="
  exit /b 0
)
echo.
echo *** [ERROR] REQ-021: Your Python program has a syntax error and cannot run. ***
echo *** File: "%HP_ENTRY%" ***
call :log "[ERROR] REQ-021: entry failed py_compile (syntax error): %HP_ENTRY%"
if exist "~preflight.err.txt" type "~preflight.err.txt"
if exist "~preflight.err.txt" type "~preflight.err.txt" >> "%LOG%"
echo.
echo *** Fix the syntax error shown above, then run this batch again. ***
if exist "~preflight.err.txt" del "~preflight.err.txt" >nul 2>&1
set "HP_PREFLIGHT_RC="
set "HP_PREFLIGHT_FAILED=1"
exit /b 0
:compute_collect_flags
rem Emit --collect-submodules flags for curated packages (sklearn, matplotlib,
rem scipy, plotly) that load submodules dynamically -- PyInstaller static analysis
rem misses them so the warn file is silent and warnfix never repairs them. The helper
rem ~collect_submodules.py double-gates: a flag is emitted only when the package is
rem BOTH imported by the user project source AND importable in the build interpreter,
rem so a hello-world in a fat env stays lean. No setlocal: HP_PYI_COLLECT must persist
rem to the caller and into the build command.
set "HP_PYI_COLLECT="
if "%HP_ENV_MODE%"=="system" exit /b 0
if defined HP_FASTPATH_USED exit /b 0
call :emit_from_base64 "~collect_submodules.py" HP_COLLECT_SUBMODULES
if exist "~collect_flags.txt" del "~collect_flags.txt" >nul 2>&1
"%HP_PY%" ~collect_submodules.py . > "~collect_flags.txt" 2>> "%LOG%"
if exist "~collect_submodules.py" del "~collect_submodules.py" >nul 2>&1
for /f "usebackq delims=" %%F in ("~collect_flags.txt") do set "HP_PYI_COLLECT=%%F"
if exist "~collect_flags.txt" del "~collect_flags.txt" >nul 2>&1
if defined HP_PYI_COLLECT call :log "[INFO] Pre-build collect-submodules:%HP_PYI_COLLECT%"
exit /b 0
:system_build_consent_gate
rem REQ-007: consent before installing PyInstaller into the user's system Python to build an EXE.
rem CI-safe (mirrors :cascade_consent_gate): HP_TEST_SYSBUILD_ANSWER (Y/N) overrides; else
rem HP_CI_LANE auto-declines with no set /p (no CI hang); else interactive prompt. The prompt
rem string is echoed unconditionally so prompt assertions see it even on auto-decline.
rem exit 0 = consent (build), exit 1 = decline (skip the build).
echo.
echo *** The standalone EXE build installs PyInstaller into your system Python. ***
echo *** This is the same PyInstaller build used for every provider -- not a special path -- and ***
echo *** its footprint is small and self-contained (it does not pin common libraries), so it is ***
echo *** unlikely to conflict with your existing packages. ***
echo.
set "HP_SYSBUILD_RAW="
if defined HP_TEST_SYSBUILD_ANSWER (
  set "HP_SYSBUILD_RAW=%HP_TEST_SYSBUILD_ANSWER%"
) else if defined HP_CI_LANE (
  set "HP_SYSBUILD_RAW=n"
) else (
  set /p "HP_SYSBUILD_RAW=  Build the standalone EXE now? [Y/N] "
)
set "HP_SYSBUILD_CHOICE=%HP_SYSBUILD_RAW:~0,1%"
if /I "%HP_SYSBUILD_CHOICE%"=="Y" (
  call :log "[INFO] REQ-007: system-Python EXE build consent: accepted."
  exit /b 0
)
call :log "[INFO] REQ-007: system-Python EXE build consent: declined."
exit /b 1
:verify_no_exe_interpreter
rem REQ-018 (2b-A.2): single-verification fallback for the NO-EXE path. When no EXE was built or
rem run (system-Python build declined, or build skipped), run the entry once via the interpreter --
rem in those providers there is no EXE deliverable, so this IS the user's run, not a throwaway.
rem Skipped when user code must not run (REQ-012) or already ran (fast path, or an EXE smoke
rem verified the build). Emits the same "Entry smoke" vocabulary + [STATUS] readout.
if defined HP_SKIP_ENTRY_SMOKE exit /b 0
if defined HP_FASTPATH_USED exit /b 0
rem skip only when an EXE smoke actually verified the build; if the EXE exists but its smoke was
rem skipped by request (HP_SKIP_EXE_SMOKERUN without HP_SKIP_ENTRY_SMOKE), still verify via the
rem interpreter so that REQ-012 "skip the EXE run" does not silently skip all verification.
if exist "dist\%ENVNAME%.exe" if not defined HP_EXE_SKIPPED exit /b 0
set "HP_PROBE_EXCEEDED="
rem Slice 2b-C: goto-based dispatch, not a parenthesized if/else block -- see the identical
rem rationale comment in :try_fast_exe -- cmd.exe freezes every %VAR% in a parenthesized block
rem to its pre-block value at parse time; launching the interpreter and reading %ERRORLEVEL%
rem inside the same parens would silently corrupt HP_SMOKE_RC for the legacy/CI branch.
if defined HP_INTERACTIVE_RUN goto :verify_no_exe_probe
call :log "[INFO] Running entry script smoke test via %HP_ENV_MODE% interpreter."
rem derived requirement: execute the smoke command inline so cmd, not our logging, owns redirection parsing.
>> "%LOG%" echo Smoke command: "%HP_PY%" "%HP_ENTRY%" ^> "~run.out.txt" 2^> "~run.err.txt"
"%HP_PY%" "%HP_ENTRY%"%HP_APP_ARGS% 1> "~run.out.txt" 2> "~run.err.txt"
set "HP_SMOKE_RC=%ERRORLEVEL%"
call :log "[INFO] Entry smoke exit=%HP_SMOKE_RC%"
if "%HP_SMOKE_RC%"=="0" (
  call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)"
  rem derived requirement: clear a stale flag from an earlier failed REQ-009 cascade tier's
  rem no-EXE run -- mirrors HP_EXE_VERIFY_FAILED's own clear-on-success at :smokerun_ok.
  set "HP_NOEXE_VERIFY_FAILED="
) else (
  call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_SMOKE_RC%)"
  rem [REQ-027] P2 honest messaging: :print_no_exe_briefing reads this to stop claiming
  rem "your code ran successfully" when it did not.
  set "HP_NOEXE_VERIFY_FAILED=1"
)
call :run_postexec_checkpoint interpreter
exit /b 0
:verify_no_exe_probe
rem Kept UNTIMED past the short probe window on purpose: a long-running app (GUI / server /
rem loop) for system-mode users has no recourse if killed, so once the probe window
rem (HP_FAILFAST_PROBE_MS) is crossed the wait becomes unbounded and the process is never
rem force-stopped. There is no cached artifact at this call site, so the probe only adds an
rem early, honest heads-up log line; the final outcome is reported either way once the
rem interpreter actually exits.
call :log "[INFO] Launching your program now via the %HP_ENV_MODE% interpreter: %HP_PY% %HP_ENTRY%"
rem [REQ-026]: entry path re-quoted here (not passed bare) plus any forwarded extra args --
rem HP_PROBE_ARGS is now a full, pre-quoted Windows Arguments string, not a single bare path
rem (see :run_failfast_probe's own header comment for the contract change).
set "HP_PROBE_EXE=%HP_PY%"
set "HP_PROBE_ARGS="%HP_ENTRY%"%HP_APP_ARGS%"
set "HP_PROBE_CWD=%CD%"
call :run_failfast_probe interpreter
rem [REQ-027] P2 honest messaging: same flag as the legacy branch above. No -1/timeout
rem ambiguity at this call site -- :run_failfast_probe never kills, so HP_SMOKE_RC is always
rem the interpreter's true final exit code once it exits, not a force-stopped placeholder.
if not "%HP_SMOKE_RC%"=="0" set "HP_NOEXE_VERIFY_FAILED=1"
rem derived requirement: clear a stale flag from an earlier failed REQ-009 cascade tier's
rem no-EXE run -- mirrors HP_EXE_VERIFY_FAILED's own clear-on-success at :smokerun_ok.
if "%HP_SMOKE_RC%"=="0" set "HP_NOEXE_VERIFY_FAILED="
call :run_postexec_checkpoint interpreter
exit /b 0
:warnfix_cascade_detect
rem REQ-009/REQ-005.10 (slice 1: detect only). After the warnfix rebuild, re-parse the
rem fresh PyInstaller warn file. If modules are STILL missing AND at least one repair
rem install failed this round, the current provider genuinely cannot supply them -- mark a
rem cascade candidate (HP_CASCADE_CANDIDATE). This slice only detects and logs; the actual
rem provider cascade (re-attempt under the next REQ-009 tier) is added in a later change.
rem Confidence gate: require BOTH the unresolved signal AND a recorded install failure so a
rem parse_warn false-positive on an already-present module does not trigger a cascade.
rem HP_TEST_FORCE_WARNFIX_UNRESOLVED=1 forces the candidate for deterministic CI coverage.
rem No setlocal: HP_CASCADE_CANDIDATE must persist to the caller.
set "HP_CASCADE_CANDIDATE="
set "HP_UNRESOLVED_AFTER="
if exist "build\%ENVNAME%\warn-%ENVNAME%.txt" (
  call :emit_from_base64 "~parse_warn.py" HP_PARSE_WARN
  "%HP_PY%" ~parse_warn.py "%ENVNAME%" > "~missing_after.txt" 2>> "%LOG%"
  if exist "~parse_warn.py" del "~parse_warn.py" >nul 2>&1
  for /f "usebackq delims=" %%M in ("~missing_after.txt") do set "HP_UNRESOLVED_AFTER=1"
)
if exist "~warnfix_repair_failed.flag" if defined HP_UNRESOLVED_AFTER set "HP_CASCADE_CANDIDATE=1"
if "%HP_TEST_FORCE_WARNFIX_UNRESOLVED%"=="1" set "HP_CASCADE_CANDIDATE=1"
set "HP_CASCADE_APPROVED="
if defined HP_CASCADE_CANDIDATE (
  call :log "[WARN] REQ-009: warnfix left modules unresolved under provider %HP_ENV_MODE%."
  call :log "[INFO] REQ-009: cascade candidate detected."
)
rem Slice 2: ask for consent. Slice 3 will consume HP_CASCADE_APPROVED to re-attempt the
rem dependency phase under the next REQ-009 provider tier. Detection-only until then.
if defined HP_CASCADE_CANDIDATE call :cascade_consent_gate
if defined HP_CASCADE_CANDIDATE if not errorlevel 1 set "HP_CASCADE_APPROVED=1"
if defined HP_CASCADE_APPROVED call :log "[INFO] REQ-009: cascade approved; will re-attempt under the next provider tier."
if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED call :log "[INFO] REQ-009: cascade declined; keeping current build."
rem Not approved (declined, timed out, or auto-declined in CI) but the candidate signal was
rem real: set a flag that persists for the rest of THIS provider's attempt (reset at the top of
rem every fresh build attempt -- see HP_NUITKA_FALLBACK_USED's neighbor above) so later
rem user-facing messages (:warn_user_code_launch) can add context the bootstrapper otherwise has
rem no other way to surface. The user may know something the automated install missed and want
rem to push through anyway -- this is a note, never a second gate.
if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED set "HP_DEP_MAYBE_INCOMPLETE=1"
if defined HP_CASCADE_CANDIDATE if not defined HP_CASCADE_APPROVED call :log "[WARN] REQ-009: dependencies for this provider may still be incomplete. If the run below fails or behaves unexpectedly, re-run and accept the cascade prompt to try a different provider, or check/edit requirements.txt yourself if you believe the automatic install missed something it shouldn't have."
if exist "~missing_after.txt" del "~missing_after.txt" >nul 2>&1
exit /b 0
:pep723_writeback
rem REQ-005.11: promotes resolved dependencies into %HP_ENTRY%'s PEP 723 header via
rem uv add --script; see docs/plan-pep723-writeback.md Part 2. Called with one argument,
rem 'fresh' or 'warnfix', for log-message differentiation only. Goto-based dispatch, not
rem nested parens, per the established cascade-dispatch convention (docs/agent-lessons-
rem learned.md "Provider-cascade dispatch is goto-based on purpose").
set "HP_PEP723_TRIGGER=%~1"
rem v1 scope gate: uv lane only. Silent -- no log line, to avoid noise on every non-uv run.
if not "%HP_ENV_MODE%"=="uv" exit /b 0
if defined HP_SKIP_PEP723_WRITEBACK (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (HP_SKIP_PEP723_WRITEBACK set)."
  exit /b 0
)
rem Defensive; technically unreachable given call-site ordering, cheap insurance only.
if defined HP_CI_SKIP_ENV exit /b 0
if not defined HP_UV_EXE exit /b 0
if not defined HP_ENTRY exit /b 0
if not exist "%HP_ENTRY%" exit /b 0
rem Packages-source-exists check runs BEFORE the confirmed-installed gate below:
rem an app with nothing to install (e.g. a stdlib-only app -- no requirements.txt is
rem ever written for it) must exit silently here, not fall into the confirmed-
rem installed gate and log a misleading "install did not fully succeed" message
rem for a round where nothing was ever attempted in the first place.
set "HP_PEP723_PKGS_SRC="
if /I "%HP_PEP723_TRIGGER%"=="fresh" set "HP_PEP723_PKGS_SRC=requirements.txt"
if /I "%HP_PEP723_TRIGGER%"=="warnfix" set "HP_PEP723_PKGS_SRC=~missing_modules.txt"
if not defined HP_PEP723_PKGS_SRC exit /b 0
if not exist "%HP_PEP723_PKGS_SRC%" exit /b 0
rem All-or-nothing confirmed-installed gate per trigger (see Part 2.0 point 3): never
rem write back from a partially-failed round.
if /I "%HP_PEP723_TRIGGER%"=="fresh" if not defined HP_UV_INSTALL_OK (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (dependency install did not fully succeed)."
  exit /b 0
)
if /I "%HP_PEP723_TRIGGER%"=="warnfix" if exist "~warnfix_repair_failed.flag" (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (one or more warnfix repairs failed)."
  exit /b 0
)
if exist "~pep723_pkgs.txt" del "~pep723_pkgs.txt" >nul 2>&1
copy /y "%HP_PEP723_PKGS_SRC%" "~pep723_pkgs.txt" >nul 2>&1
if errorlevel 1 goto :pep723_writeback_cleanup
call :emit_from_base64 "~pep723_writeback.py" HP_PEP723_WRITEBACK
if errorlevel 1 goto :pep723_writeback_cleanup
if exist "~pep723_result.txt" del "~pep723_result.txt" >nul 2>&1
"%HP_PY%" "~pep723_writeback.py" "%HP_ENTRY%" "%HP_UV_EXE%" "%HP_PY%" "~pep723_pkgs.txt" > "~pep723_result.txt" 2>> "%LOG%"
set "HP_PEP723_RESULT="
for /f "usebackq delims=" %%R in ("~pep723_result.txt") do set "HP_PEP723_RESULT=%%R"
if exist "~pep723_result.txt" del "~pep723_result.txt" >nul 2>&1
if exist "~pep723_writeback.py" del "~pep723_writeback.py" >nul 2>&1
if not defined HP_PEP723_RESULT (
  call :log "[WARN] REQ-005.11: PEP 723 write-back helper produced no output; continuing."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT:~0,3%"=="OK:" (
  call :log "[INFO] REQ-005.11: PEP 723 header write-back succeeded via uv add --script."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT%"=="SKIP:no_packages" (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (no packages to write)."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT%"=="SKIP:non_utf8" (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (entry file is not UTF-8)."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT%"=="SKIP:file_locked" (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (entry file is locked)."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT%"=="SKIP:lockfile" (
  call :log "[INFO] REQ-005.11: PEP 723 write-back skipped (a .py.lock sidecar already exists)."
  goto :pep723_writeback_cleanup
)
if "%HP_PEP723_RESULT:~0,6%"=="ERROR:" (
  call :log "[WARN] REQ-005.11: PEP 723 header write-back failed (%HP_PEP723_RESULT%); continuing."
  goto :pep723_writeback_cleanup
)
call :log "[WARN] REQ-005.11: PEP 723 header write-back returned an unrecognized result; continuing."
:pep723_writeback_cleanup
if exist "~pep723_pkgs.txt" del "~pep723_pkgs.txt" >nul 2>&1
set "HP_PEP723_TRIGGER="
set "HP_PEP723_PKGS_SRC="
set "HP_PEP723_RESULT="
exit /b 0

:pvw_known_idempotent_run
rem REQ-005.13 (Tier 2): actually runs %HP_ENTRY% via uvx autopep723 as opt-in execute-mode
rem dependency discovery. Never fails the lane: any nonzero outcome here just leaves the
rem Default Path (pyproject.toml/PEP 723 header/pipreqs, all still to come) untouched.
call :log "[INFO] REQ-005.13: HP_PVW_KNOWN_IDEMPOTENT set; running entry via uvx autopep723 for execute-mode discovery."
call :emit_from_base64 "~pvw_known_idempotent.py" HP_PVW_IDEMPOTENT
if errorlevel 1 (
  call :log "[WARN] REQ-005.13: could not write execute-mode discovery helper; falling back to Default Path."
  exit /b 0
)
rem derived requirement: only stderr is redirected here, never stdout -- the helper's own
rem run step inherits stdout so the user's script output prints live to the console; the
rem helper deliberately writes its own RAN:/ERROR: result marker to stderr for exactly this
rem reason (see tools/pvw_known_idempotent.py's module docstring).
if exist "~pvw_idempotent_result.txt" del "~pvw_idempotent_result.txt" >nul 2>&1
"%HP_PY%" "~pvw_known_idempotent.py" "%HP_ENTRY%" "%HP_UVX_EXE%" "%HP_UV_EXE%" "%HP_PY%" 2> "~pvw_idempotent_result.txt"
set "HP_PVW_RUN_RC=%ERRORLEVEL%"
set "HP_PVW_RESULT="
for /f "usebackq delims=" %%R in ("~pvw_idempotent_result.txt") do set "HP_PVW_RESULT=%%R"
if exist "~pvw_idempotent_result.txt" del "~pvw_idempotent_result.txt" >nul 2>&1
if exist "~pvw_known_idempotent.py" del "~pvw_known_idempotent.py" >nul 2>&1
if not "%HP_PVW_RUN_RC%"=="0" (
  call :log "[WARN] REQ-005.13: execute-mode discovery run failed (%HP_PVW_RESULT%); falling back to Default Path discovery."
  set "HP_PVW_RUN_RC="
  set "HP_PVW_RESULT="
  exit /b 0
)
call :log "[INFO] REQ-005.13: execute-mode discovery run succeeded (%HP_PVW_RESULT%)."
set "HP_PVW_RUN_RC="
set "HP_PVW_RESULT="
rem Only the entry file's PEP 723 header was updated (uv add --script) -- requirements.txt
rem itself still needs populating from it, exactly like the pre-existing-header case a few
rem lines below already does. Reuses that existing subroutine rather than a new writer.
set "HP_PVW_REQ=~requirements.pvw_idempotent.txt"
if exist "%HP_PVW_REQ%" del "%HP_PVW_REQ%" >nul 2>&1
call :extract_pep723_requirements "%HP_ENTRY%" "%HP_PVW_REQ%"
if exist "%HP_PVW_REQ%" for %%S in ("%HP_PVW_REQ%") do if %%~zS GTR 0 (
  copy /y "%HP_PVW_REQ%" "requirements.txt" >nul 2>&1
  call :log "[INFO] DEP_SOURCE=pvw_idempotent"
  call :log "[TRACE] dep source selected: pvw_idempotent"
)
if exist "%HP_PVW_REQ%" del "%HP_PVW_REQ%" >nul 2>&1
set "HP_PVW_REQ="
exit /b 0

:cascade_consent_gate
rem REQ-009/REQ-005.10: require explicit consent before cascading to the next provider tier.
rem CI-safe (mirrors :conda_binary_corrupt heal prompt): HP_TEST_CASCADE_ANSWER (Y/N) overrides;
rem otherwise HP_CI_LANE auto-declines with no prompt (no wait in CI) UNLESS
rem HP_TEST_FORCE_INTERACTIVE_CASCADE forces the real-user branch for deterministic CI coverage
rem of the timed prompt itself (mirrors HP_TEST_FORCE_PICKER's use for :pick_entry_interactive).
rem A real interactive user gets a TIMED choice /T prompt (default: 30s), not an unbounded
rem set /p -- if nobody answers in time it defaults to N (decline), so an unattended run still
rem tries the current build once rather than hanging the whole bootstrap forever. Goto-based
rem dispatch throughout (not nested parens) so `choice` + `if errorlevel` reads are never inside
rem a block that could freeze an earlier value -- see docs/agent-lessons-learned.md's
rem "Provider-cascade dispatch is goto-based on purpose". exit 0 = approved, exit 1 = declined.
echo.
echo *** Some dependencies could not be installed under the current Python provider. ***
echo.
set "HP_CASCADE_CHOICE="
if defined HP_TEST_CASCADE_ANSWER goto :cascade_consent_from_override
if defined HP_CI_LANE if not defined HP_TEST_FORCE_INTERACTIVE_CASCADE goto :cascade_consent_ci_decline
goto :cascade_consent_interactive

:cascade_consent_from_override
set "HP_CASCADE_RAW=%HP_TEST_CASCADE_ANSWER%"
set "HP_CASCADE_CHOICE=%HP_CASCADE_RAW:~0,1%"
goto :cascade_consent_resolve

:cascade_consent_ci_decline
set "HP_CASCADE_CHOICE=N"
goto :cascade_consent_resolve

:cascade_consent_interactive
set "HP_CASCADE_T=30"
if defined HP_TEST_FORCE_INTERACTIVE_CASCADE set "HP_CASCADE_T=2"
where choice >nul 2>&1
if errorlevel 1 goto :cascade_consent_no_choice_exe
choice /C YN /N /T %HP_CASCADE_T% /D N /M "  Try the next Python provider to resolve them? [Y/N, defaults to N after %HP_CASCADE_T%s] "
if errorlevel 2 goto :cascade_consent_ci_decline
set "HP_CASCADE_CHOICE=Y"
goto :cascade_consent_resolve

:cascade_consent_no_choice_exe
rem no choice.exe on this image (stripped install) -- keep the safe default (decline).
set "HP_CASCADE_CHOICE=N"
goto :cascade_consent_resolve

:cascade_consent_resolve
if /I "%HP_CASCADE_CHOICE%"=="Y" (
  call :log "[INFO] REQ-009: cascade consent: accepted."
  exit /b 0
)
call :log "[INFO] REQ-009: cascade consent: declined."
exit /b 1
:dll_bundle_recover
rem CLAUDE.md Active Backlog Item 24 / docs/prd-conda-native-dll-bundling.md: reactive,
rem bounded repair loop for a conda-installed native extension whose compiled .pyd depends
rem on a shared DLL PyInstaller's static analysis cannot trace (conda's own Library\bin
rem convention -- e.g. eccodes.dll for pygrib). Mirrors :hidden_import_recover's bounded-
rem iteration/tried-list shape (see docs/agent-lessons-learned.md's "--hidden-import
rem auto-recovery must stay STRICT"), but detects at BUILD time rather than waiting for the
rem guaranteed runtime crash: PyInstaller's own build log already announces an unresolved
rem native dependency ("WARNING: Library not found: could not resolve 'X.dll'") before the
rem EXE is ever smoke-run. Requirement 1's own CI experiment (PR #415,
rem self.gribapi_hook_probe.hidden_import, conclusive:true, hiddenImportHelped:false)
rem confirmed forcing --hidden-import=gribapi does NOT make hook-gribapi.py bundle
rem eccodes.dll for a pygrib-only build, ruling out that free-lunch shortcut and motivating
rem this loop. goto-based (not a parenthesized block) so each %VAR% reads its runtime
rem value, not a parse-time one -- see docs/agent-lessons-learned.md's "Provider-cascade
rem dispatch is goto-based on purpose".
rem CLAUDE.md Item 29: this subroutine can now be called a SECOND time per fresh build
rem attempt, after :hidden_import_recover's own loop finishes -- a hidden-import rebuild's
rem own --collect-submodules=X can pull in a package whose compiled extension needs a native
rem DLL never checked before (confirmed via a real pyproj/proj_9.dll failure). Reset this
rem call's own "did I actually bundle something" signal FIRST, before any early-return path,
rem so the caller has a reliable, per-call-scoped flag -- HP_DLL_ITER is NOT reliable for this
rem across two calls (an early "nothing detected" return below never resets it, so a stale
rem value from an EARLIER call could otherwise look like fresh repair activity).
set "HP_DLL_REPAIRED="
if not exist "dist\%ENVNAME%.exe" exit /b 0
if not defined HP_LOG_SIZE_BEFORE exit /b 0
rem Requirement 3: detection is cheap and provider-agnostic -- always checked first,
rem regardless of provider or Nuitka-vs-PyInstaller build, so the caveat panel's
rem "detected, repair skipped" state (see docs/open-questions.md) is backed by a real
rem signal instead of silence. Only the actual locate-and-bundle ACTION below is gated.
call :emit_from_base64 "~dll_bundle_scan.py" HP_DLL_BUNDLE_SCAN
"%HP_PY%" ~dll_bundle_scan.py --detect "%LOG%" "%HP_LOG_SIZE_BEFORE%" > "~dll_detect.txt" 2>> "%LOG%"
if exist "~dll_bundle_scan.py" del "~dll_bundle_scan.py" >nul 2>&1
set "HP_DLL_DETECTED="
for /f "usebackq delims=" %%N in ("~dll_detect.txt") do set "HP_DLL_DETECTED=%%N"
if exist "~dll_detect.txt" del "~dll_detect.txt" >nul 2>&1
if not defined HP_DLL_DETECTED exit /b 0
rem CodeRabbit finding: :run_entry_smoke (and therefore :dll_bundle_recover) can run more than
rem once per process during a REQ-009 provider cascade re-entry. HP_NEXT_DLL/HP_DLL_ITER are
rem normally reset later (line ~4017/inside the loop), which is AFTER the skipped_nuitka/
rem skipped_non_conda exits below -- so a stale value left by an EARLIER call's successful
rem repair would leak into a LATER call's skip row via :emit_dll_bundle_row's own
rem "$dll = if ($next) { $next } else { $detected }" fallback. Reset both here, before either
rem skip branch can read them.
set "HP_NEXT_DLL="
set "HP_DLL_ITER=0"
rem :log echoes UNQUOTED (see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry) --
rem a DLL basename can legally contain &, |, <, or > on Windows, which cmd.exe would otherwise
rem reinterpret as a live redirection/pipe operator once substituted into an unquoted echo line,
rem corrupting the log line and/or creating a stray file. Sanitize a DISPLAY-ONLY copy here; the
rem raw HP_DLL_DETECTED is never used for anything functional (no file lookup happens on it --
rem the real Library\bin search is redone from scratch, on the raw warning text, inside
rem :dll_bundle_loop below), so this substitution cannot desync any matching logic.
rem CodeRabbit finding: `call :log` performs a SECOND cmd.exe expansion pass on its own
rem argument text (the documented "call re-parses its command line" behavior), so a raw
rem % or ^ surviving into HP_DLL_DETECTED_SAFE could still expand an unrelated environment
rem variable (e.g. a crafted "%SOME_SECRET%.dll" warning text) or alter escaping on that
rem second pass, even though &/|/</> are already neutralized.
set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED%"
set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:&=_%"
set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:|=_%"
set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:<=_%"
set "HP_DLL_DETECTED_SAFE=%HP_DLL_DETECTED_SAFE:>=_%"
rem derived requirement: a real CI-confirmed bug -- cmd.exe's own %VAR:%%=X% doubled-percent
rem idiom does NOT reliably strip a literal percent sign (confirmed via
rem batch.dll_bundle.pct_sanitizer's live cmd.exe fixture: the substitution silently produced
rem an EMPTY value instead of the expected text, an undocumented cmd.exe parsing quirk this
rem repo's own reasoning got wrong on the first attempt). A SECOND real CI-confirmed bug then
rem hit an inline -Command replacement of it too: a lone, unpaired % anywhere on the same
rem cmd.exe logical line as %LOG% got silently paired with %LOG%'s own opening %, deleting
rem everything between them as one bogus undefined-variable reference. Both bugs share one
rem root cause -- literal % text living on a cmd.exe-parsed line at all -- so the actual fix
rem is tools/dll_pct_sanitize.ps1, a real emitted .ps1 file: its content is never parsed by
rem cmd.exe's tokenizer (only the outer -File "path" arg... line is, and plain argv has no
rem %-pairing hazard). See docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry for
rem the full trace of both bugs.
call :emit_from_base64 "~dll_pct_sanitize.ps1" HP_DLL_PCT_SANITIZE
powershell -NoProfile -ExecutionPolicy Bypass -File "~dll_pct_sanitize.ps1" HP_DLL_DETECTED_SAFE "~dll_pct_safe.txt" >> "%LOG%" 2>&1
if exist "~dll_pct_sanitize.ps1" del "~dll_pct_sanitize.ps1" >nul 2>&1
set "HP_DLL_DETECTED_SAFE="
for /f "usebackq delims=" %%X in ("~dll_pct_safe.txt") do set "HP_DLL_DETECTED_SAFE=%%X"
if exist "~dll_pct_safe.txt" del "~dll_pct_safe.txt" >nul 2>&1
rem Requirement 6: identical guard to :hidden_import_recover's own, for the identical
rem reason -- --add-binary is a PyInstaller-specific flag with no Nuitka equivalent wired
rem up here. Skip (not attempt-and-fail) rather than risk rebuilding a working Tier-A EXE
rem via the wrong tool.
if defined HP_NUITKA_FALLBACK_USED (
  call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%'; skipping repair: dist\%ENVNAME%.exe was built via the fallback build system (Nuitka), which has its own DLL discovery mechanism, not this PyInstaller-specific repair."
  call :emit_dll_bundle_row skipped_nuitka
  exit /b 0
)
rem Requirement 3: the actual bundling ACTION only makes sense under the conda provider --
rem Library\bin is conda's own shared-DLL convention, meaningless under uv/embed/venv/system
rem (those install from PyPI wheels, which are expected to vendor their own native deps).
if not "%HP_ENV_MODE%"=="conda" (
  call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%'; native-DLL bundling repair requires the conda provider (current provider: %HP_ENV_MODE%); skipping."
  call :emit_dll_bundle_row skipped_non_conda
  exit /b 0
)
if exist "~dll_bundle_tried.txt" del "~dll_bundle_tried.txt" >nul 2>&1
set "HP_DLL_ITER=0"
rem CLAUDE.md Item 29: HP_PYI_DLLBIND is deliberately NOT reset here -- it is now reset once
rem per fresh build attempt, in :run_entry_smoke, so a SECOND call to this subroutine (the
rem post-hidden-import-recovery pass) keeps whatever this label's own earlier call already
rem accumulated instead of silently discarding it.
set "HP_DLL_FAILED="
set "HP_DLL_EXHAUSTED="
rem derived requirement: real CI evidence (self.layered_e2e.chain, cache lane, 2026-08-07) --
rem HP_PY_DIR (from %%~dpI) always ends in exactly ONE trailing backslash. Quoted as
rem "%HP_PY_DIR%" immediately before another quoted argument, Python's own argv parser
rem (the standard Windows CommandLineToArgvW backslash-quote-parity rule, the same hazard
rem already documented for findstr in docs/agent-lessons-learned.md, but here hitting
rem python.exe's own C-runtime startup instead) treats that single trailing backslash as
rem ESCAPING the closing quote rather than closing it -- the quoted region never actually
rem closes, silently merging conda_env_dir with the next argument (the tried-file path) into
rem one garbage string. locate_dll() then fails os.path.isdir() on that garbage and reports
rem "could not locate" even when the real DLL is genuinely sitting under Library\bin --
rem confirmed for real: eccodes.dll IS present in the eccodes-2.48.0-h3bec8ca_0 conda-forge
rem package at exactly Library\bin\eccodes.dll, yet self.layered_e2e.chain's own CI run still
rem reported "could not locate a matching file". Deterministic, not flaky -- HP_PY_DIR always
rem ends in one backslash by construction, so this fires on every single conda-provider run.
rem Fixed the same way lessons-learned's findstr entry recommends: double the trailing
rem backslash so an EVEN count precedes the closing quote, collapsing back to the intended
rem single backslash on the receiving end.
set "HP_PY_DIR_ARG=%HP_PY_DIR%"
if defined HP_PY_DIR_ARG set "HP_PY_DIR_ARG=%HP_PY_DIR_ARG%\"
:dll_bundle_loop
call :emit_from_base64 "~dll_bundle_scan.py" HP_DLL_BUNDLE_SCAN
"%HP_PY%" ~dll_bundle_scan.py "%LOG%" "%HP_LOG_SIZE_BEFORE%" "%HP_PY_DIR_ARG%" "~dll_bundle_tried.txt" > "~next_dll.txt" 2>> "%LOG%"
if exist "~dll_bundle_scan.py" del "~dll_bundle_scan.py" >nul 2>&1
set "HP_NEXT_DLL="
set "HP_NEXT_DLL_PATH="
for /f "usebackq tokens=1,2 delims=|" %%A in ("~next_dll.txt") do (
  set "HP_NEXT_DLL=%%A"
  set "HP_NEXT_DLL_PATH=%%B"
)
if not defined HP_NEXT_DLL (
  if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1
  goto :dll_bundle_recover_done
)
if %HP_DLL_ITER% GEQ 3 (
  rem derived requirement: CLAUDE.md Item 25 -- a real candidate was just found, the
  rem "if not defined HP_NEXT_DLL" early-return above already ruled out the empty case, but
  rem the 3-iteration cap discards it here without a trace. HP_DLL_EXHAUSTED distinguishes
  rem this from a clean "repaired" outcome at :dll_bundle_recover_done below -- without it,
  rem HP_DLL_ITER GEQ 1, true because 3 DLLs were genuinely bundled in earlier iterations, alone
  rem would claim "Native-DLL bundling complete" even though a real, locatable DLL was left
  rem unbundled.
  set "HP_DLL_EXHAUSTED=1"
  if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1
  goto :dll_bundle_recover_done
)
rem Append via file content (type), never via %VAR% expansion on an echo/set line --
rem a DLL basename can legally contain a space or a cmd.exe metacharacter (^, &, or |),
rem which would corrupt or split the command line if routed through argv/echo text.
type "~next_dll.txt">>"~dll_bundle_tried.txt"
echo.>>"~dll_bundle_tried.txt"
if exist "~next_dll.txt" del "~next_dll.txt" >nul 2>&1
rem Same :log-unquoted-echo hazard as HP_DLL_DETECTED above -- sanitize DISPLAY-ONLY copies.
rem The raw HP_NEXT_DLL/HP_NEXT_DLL_PATH are still used unchanged for the tried-file (pure file
rem content, never shell-expanded) and the quoted --add-binary argument below.
set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL%"
set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:&=_%"
set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:|=_%"
set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:<=_%"
set "HP_NEXT_DLL_SAFE=%HP_NEXT_DLL_SAFE:>=_%"
set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH%"
set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:&=_%"
set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:|=_%"
set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:<=_%"
set "HP_NEXT_DLL_PATH_SAFE=%HP_NEXT_DLL_PATH_SAFE:>=_%"
rem derived requirement: same %-parsing hazard as HP_DLL_DETECTED_SAFE above (two real
rem CI-confirmed bugs -- see that comment and docs/agent-lessons-learned.md's ":log echoes
rem UNQUOTED" entry for the full trace), fixed the same way: tools/dll_pct_sanitize.ps1, a
rem real emitted .ps1 file whose content cmd.exe never parses. Two separate output files
rem (one per env var), each read back via a plain single-value "for /f ... do set" -- this
rem repo bans delayed expansion (!VAR!) repo-wide, which a multi-line parse would need.
call :emit_from_base64 "~dll_pct_sanitize.ps1" HP_DLL_PCT_SANITIZE
powershell -NoProfile -ExecutionPolicy Bypass -File "~dll_pct_sanitize.ps1" HP_NEXT_DLL_SAFE "~dll_pct_safe_a.txt" HP_NEXT_DLL_PATH_SAFE "~dll_pct_safe_b.txt" >> "%LOG%" 2>&1
if exist "~dll_pct_sanitize.ps1" del "~dll_pct_sanitize.ps1" >nul 2>&1
set "HP_NEXT_DLL_SAFE="
set "HP_NEXT_DLL_PATH_SAFE="
for /f "usebackq delims=" %%X in ("~dll_pct_safe_a.txt") do set "HP_NEXT_DLL_SAFE=%%X"
for /f "usebackq delims=" %%X in ("~dll_pct_safe_b.txt") do set "HP_NEXT_DLL_PATH_SAFE=%%X"
if exist "~dll_pct_safe_a.txt" del "~dll_pct_safe_a.txt" >nul 2>&1
if exist "~dll_pct_safe_b.txt" del "~dll_pct_safe_b.txt" >nul 2>&1
set /a HP_DLL_ITER+=1
set "HP_PYI_DLLBIND=%HP_PYI_DLLBIND% --add-binary "%HP_NEXT_DLL_PATH%;.""
call :log "[REPAIR][DLL_BUNDLE] Bundling native DLL dependency: %HP_NEXT_DLL_SAFE% (found at %HP_NEXT_DLL_PATH_SAFE%); rebuilding EXE (iter %HP_DLL_ITER%/3)."
if exist "%ENVNAME%.spec" (set "HP_DLL_SPEC_PRE=1") else (set "HP_DLL_SPEC_PRE=")
for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ"
rem CLAUDE.md Item 29: thread HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT through this rebuild
rem too -- when this is the SECOND call (after :hidden_import_recover), omitting them would
rem silently drop every hidden-import fix already applied, the mirror-image of the bug
rem already fixed for HP_PYI_DLLBIND reaching :hidden_import_recover's own rebuild (see
rem docs/agent-interconnect.md). Empty/undefined on a first call, so no behavior change there.
"%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% %HP_PYI_DLLBIND% %HP_PYI_HIDDEN_IMPORTS% %HP_PYI_HID_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  call :log "[ERROR][DLL_BUNDLE] PyInstaller rebuild failed while bundling native DLL dependency: %HP_NEXT_DLL_SAFE%; the previous build may no longer be valid."
  set "HP_BOOTSTRAP_STATE=error"
  set "HP_DLL_FAILED=1"
  call :emit_dll_bundle_row failed_rebuild
  goto :dll_bundle_recover_done
)
if not exist "dist\%ENVNAME%.exe" (
  call :log "[ERROR][DLL_BUNDLE] PyInstaller did not produce dist\%ENVNAME%.exe while bundling native DLL dependency: %HP_NEXT_DLL_SAFE%."
  set "HP_BOOTSTRAP_STATE=error"
  set "HP_DLL_FAILED=1"
  call :emit_dll_bundle_row failed_missing_exe
  goto :dll_bundle_recover_done
)
if not defined HP_DLL_SPEC_PRE if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1
if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1
goto :dll_bundle_loop
:dll_bundle_recover_done
rem derived requirement: a real bug caught by review -- HP_DLL_ITER GEQ 1 alone is NOT
rem proof of success; both failure branches above also leave it >= 1 after their own
rem increment. HP_DLL_FAILED is the actual success/failure signal; the "complete" line
rem must never fire on a genuine rebuild failure (mirrors the warnfix-rebuild's own
rem "never claim success without checking" precedent -- see docs/agent-lessons-learned.md).
if defined HP_DLL_FAILED goto :dll_bundle_recover_exit
if defined HP_DLL_EXHAUSTED (
  call :log "[WARN][DLL_BUNDLE] Native-DLL bundling reached its 3-attempt cap with another native-DLL dependency still detected (first seen: '%HP_DLL_DETECTED_SAFE%'); %HP_DLL_ITER% DLL(s) were bundled, but at least one more remains unbundled -- the EXE may still fail to load it."
  call :emit_dll_bundle_row exhausted
  goto :dll_bundle_recover_exit
)
if %HP_DLL_ITER% GEQ 1 (
  call :log "[REPAIR][DLL_BUNDLE] Native-DLL bundling complete (%HP_DLL_ITER% DLL(s) added); EXE will be re-verified next."
  call :emit_dll_bundle_row repaired
  set "HP_DLL_REPAIRED=1"
) else (
  call :log "[INFO][DLL_BUNDLE] Detected native-DLL warning for '%HP_DLL_DETECTED_SAFE%' but could not locate a matching file under the conda env's Library\bin; skipping."
  call :emit_dll_bundle_row unlocatable
)
:dll_bundle_recover_exit
if exist "~dll_bundle_tried.txt" del "~dll_bundle_tried.txt" >nul 2>&1
set "HP_DLL_SPEC_PRE="
set "HP_DLL_FAILED="
set "HP_DLL_EXHAUSTED="
exit /b 0
:emit_dll_bundle_row
rem CodeRabbit finding on PR #414: ":dll_bundle_recover"'s detected/skipped/repaired/
rem unlocatable/failed outcomes previously only reached :log's console text, with no
rem machine-readable record. %1 is always one of a small set of literal state tokens
rem (skipped_nuitka/skipped_non_conda/repaired/unlocatable/exhausted/failed_rebuild/
rem failed_missing_exe -- CLAUDE.md Item 25 added "exhausted": the 3-iteration cap was
rem hit with a real, locatable candidate still pending, distinct from a clean "repaired")
rem written directly in THIS file, never derived from external
rem content, so passing it as a call argument is safe. The DLL name/provider/iteration
rem are pulled INSIDE PowerShell via [Environment]::GetEnvironmentVariable rather than
rem %VAR% cmd.exe substitution into the -Command text -- same reasoning as the
rem HP_DLL_DETECTED_SAFE/HP_NEXT_DLL_SAFE display-only sanitization above, but this
rem time protecting cmd.exe's OWN command-line parsing (& and | are metacharacters even
rem inside a quoted argument) rather than :log's unquoted echo.
rem docs/open-questions.md item 1 (answered yes): captured BEFORE the HP_NDJSON early-return
rem below, so the post-flight caveat panel's DLL-specific hint (:pfb_dll_hint) still works even
rem when NDJSON emission itself is disabled -- this call's own %~1 state token is the same value
rem either way. See the fresh-build-attempt reset of this variable above for its reset contract.
set "HP_DLL_HINT_STATE=%~1"
if not defined HP_NDJSON exit /b 0
set "HP_DLL_ROW_STATE=%~1"
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$state = [Environment]::GetEnvironmentVariable('HP_DLL_ROW_STATE');" ^
  "$provider = [Environment]::GetEnvironmentVariable('HP_ENV_MODE');" ^
  "$detected = [Environment]::GetEnvironmentVariable('HP_DLL_DETECTED');" ^
  "$next = [Environment]::GetEnvironmentVariable('HP_NEXT_DLL');" ^
  "$iterRaw = [Environment]::GetEnvironmentVariable('HP_DLL_ITER');" ^
  "$iter = 0; [void][int]::TryParse($iterRaw, [ref]$iter);" ^
  "$dll = if ($next) { $next } else { $detected };" ^
  "$pass = -not ($state -like 'failed_*');" ^
  "$row = [ordered]@{ id='self.dll_bundle.recover'; pass=$pass; details=[ordered]@{ state=$state; provider=$provider; dll=$dll; iteration=$iter } } | ConvertTo-Json -Compress -Depth 8;" ^
  "Add-Content -Path '%HP_NDJSON%' -Value $row -Encoding ASCII" >> "%LOG%" 2>&1
exit /b 0
:hidden_import_recover
rem Slice 2 (REQ-016): strict, double-gated --hidden-import auto-recovery loop.
rem Acts ONLY on `ModuleNotFoundError: No module named X` where X is INSTALLED in the
rem build interpreter (~hidden_import_scan.py enforces both gates), so a user typo /
rem ImportError / circular-import never triggers a rebuild. Bounded to 3 rebuilds; the
rem helper's already-tried list plus the iter cap guarantee the loop cannot run forever.
rem Sets HP_EXE_EXIT to the final EXE exit so the caller re-checks success. goto-based
rem (not a parenthesized block) so each %VAR% reads its runtime value, not a parse-time one.
rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): reset this call's own "did I
rem actually rebuild" signal FIRST, before any early-return path -- mirrors HP_DLL_REPAIRED's
rem identical reasoning in :dll_bundle_recover. Lets the caller (the second :dll_bundle_recover
rem pass) skip its own scan entirely when this call did nothing, instead of always re-scanning
rem an unchanged log for no reason.
set "HP_HIDDEN_REPAIRED="
if not exist "dist\%ENVNAME%.exe" exit /b 0
rem AV-Safe Build Path (requirement 4 follow-up): this loop's ONLY repair mechanism is a
rem PyInstaller rebuild with --hidden-import flags -- a PyInstaller-specific mechanism that
rem does not apply to a Nuitka-produced EXE. If dist\%ENVNAME%.exe was built via Tier A
rem (:try_nuitka_tier_a, HP_NUITKA_FALLBACK_USED=1), skip entirely rather than silently
rem rebuilding via PyInstaller here -- that would risk reproducing the very failure Tier A
rem exists to route around, or clobbering a working Nuitka build with a broken PyInstaller
rem one. Nuitka has its own, different missing-import mechanism (--include-module /
rem --follow-import-to); wiring that up is out of scope for this fix.
if defined HP_NUITKA_FALLBACK_USED (
  call :log "[INFO][HIDDEN_IMPORT] Skipping --hidden-import auto-recovery: dist\%ENVNAME%.exe was built via the fallback build system (Nuitka), which uses a different missing-import mechanism than PyInstaller's --hidden-import flag."
  exit /b 0
)
rem CLAUDE.md Item 28: pair --collect-submodules=X with each --hidden-import=X this loop adds.
rem A --hidden-import target only guarantees PyInstaller follows X's own statically-discovered
rem imports; it does NOT guarantee every real submodule under X/ is bundled. A compiled C
rem extension elsewhere in the app (invisible to PyInstaller's source scan the same way the
rem original missing import was) can still need a submodule of X that X's own __init__.py never
rem references -- confirmed via pygrib 2.1.8's real source (`from packaging import version` inside
rem a Cython extension, needing packaging.version even after --hidden-import=packaging alone).
rem Broader than strictly necessary (collects every submodule of X, not just the one actually
rem needed) but structurally safe: X is already find_spec-confirmed installed by
rem ~hidden_import_scan.py's own gate, so this never targets an unresolvable package name, and it
rem never guesses AT a package name the way inferring one from the failure text would.
rem CLAUDE.md Item 29: HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT are deliberately NOT reset here
rem (unlike HP_HIDDEN_ITER/HP_HIDDEN_TRIED just below, which DO get a fresh budget every call).
rem This subroutine can now be called a SECOND time per fresh build attempt (after a
rem :dll_bundle_recover repair -- see that call site) -- resetting these here would silently
rem drop every hidden-import flag the FIRST call already accumulated from this SECOND call's
rem own rebuild command, regressing an already-fixed package back to ModuleNotFoundError. Both
rem are reset once per fresh build attempt instead, in :run_entry_smoke, mirroring the identical
rem fix already applied to HP_PYI_DLLBIND in :dll_bundle_recover.
set "HP_HIDDEN_ITER=0"
set "HP_HIDDEN_TRIED="
rem preserve a user pre-existing spec across recovery rebuilds (the main-build spec-preexist
rem flag was already consumed before run_exe_smokerun ran).
set "HP_HID_SPEC_PRE="
if exist "%ENVNAME%.spec" set "HP_HID_SPEC_PRE=1"
call :warn_user_code_launch hidden_import
:hidden_import_loop
rem run the EXE with a 30s cap and capture combined output for the scan. A cap is
rem essential here: once recovery fixes a missing import the app may proceed into a
rem long-running phase (server/GUI), and an uncapped run would hang the bootstrapper.
set "HP_EXE_EXIT="
rem CLAUDE.md Active Backlog Item 38: this --hidden-import auto-recovery diagnostic re-run still
rem runs from dist\ (current working directory (CWD) = dist), unlike :run_exe_smokerun's own
rem primary verification (now app root). Deliberately left as a documented, deferred follow-up --
rem the write (~exe_out.txt, inside dist\) and the later read-back (explicit "dist\~exe_out.txt")
rem a few lines below are already internally consistent with each other regardless of which CWD is
rem chosen, and no test depends on this specific CWD (selfapps_hidden_import*.ps1's stub apps
rem locate their own token file via sys.argv[0], not a CWD-relative path, so they are unaffected
rem either way -- see docs/agent-interconnect.md "Single-verification smoke model"). Re-derive
rem whether to unify this too the next time this subroutine is touched.
pushd dist
for /f "usebackq delims=" %%X in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$si=New-Object System.Diagnostics.ProcessStartInfo;$si.FileName='%ENVNAME%.exe';$si.UseShellExecute=$false;$si.RedirectStandardOutput=$true;$si.RedirectStandardError=$true;$p=[System.Diagnostics.Process]::Start($si);$so=$p.StandardOutput.ReadToEndAsync();$se=$p.StandardError.ReadToEndAsync();$done=$p.WaitForExit(30000);if(-not $done){try{$p.Kill()}catch{}};($so.Result+$se.Result)|Set-Content -Path '~exe_out.txt' -Encoding ASCII;if($done){$p.ExitCode}else{-1}"`) do set "HP_EXE_EXIT=%%X"
if not defined HP_EXE_EXIT set "HP_EXE_EXIT=-1"
popd
if "%HP_EXE_EXIT%"=="0" goto :hidden_import_recover_done
if %HP_HIDDEN_ITER% GEQ 3 goto :hidden_import_recover_done
call :emit_from_base64 "~hidden_import_scan.py" HP_HIDDEN_IMPORT_SCAN
"%HP_PY%" ~hidden_import_scan.py "dist\~exe_out.txt" %HP_HIDDEN_TRIED% > "~next_hidden.txt" 2>> "%LOG%"
if exist "~hidden_import_scan.py" del "~hidden_import_scan.py" >nul 2>&1
set "HP_NEXT_HIDDEN="
for /f "usebackq delims=" %%M in ("~next_hidden.txt") do set "HP_NEXT_HIDDEN=%%M"
if exist "~next_hidden.txt" del "~next_hidden.txt" >nul 2>&1
if not defined HP_NEXT_HIDDEN goto :hidden_import_recover_done
set /a HP_HIDDEN_ITER+=1
set "HP_HIDDEN_REPAIRED=1"
set "HP_PYI_HIDDEN_IMPORTS=%HP_PYI_HIDDEN_IMPORTS% --hidden-import=%HP_NEXT_HIDDEN%"
set "HP_PYI_HID_COLLECT=%HP_PYI_HID_COLLECT% --collect-submodules=%HP_NEXT_HIDDEN%"
set "HP_HIDDEN_TRIED=%HP_HIDDEN_TRIED% %HP_NEXT_HIDDEN%"
call :log "[REPAIR][HIDDEN_IMPORT] Adding --hidden-import=%HP_NEXT_HIDDEN% --collect-submodules=%HP_NEXT_HIDDEN%; rebuilding EXE (iter %HP_HIDDEN_ITER%/3)."
rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): advance HP_LOG_SIZE_BEFORE to
rem right before THIS rebuild, mirroring :dll_bundle_loop's own identical pattern for its own
rem rebuilds. Narrows the SECOND :dll_bundle_recover pass's own scan window to just the LAST
rem hidden-import rebuild's output (rather than everything since the first DLL-bundle pass,
rem which included earlier, already-resolved rebuilds too) -- tighter and more precise, even
rem though the wider window was not observed to cause a false re-detection in practice (an
rem already-bundled DLL's own warning does not reappear in a later rebuild that still includes
rem its --add-binary flag).
for %%Z in ("%LOG%") do set "HP_LOG_SIZE_BEFORE=%%~zZ"
"%HP_PY%" -m PyInstaller -y --onefile --clean --log-level WARN %HP_PYI_EXPAT% %HP_PYI_COLLECT% %HP_PYI_DLLBIND% %HP_PYI_HIDDEN_IMPORTS% %HP_PYI_HID_COLLECT% --name "%ENVNAME%" "%HP_ENTRY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  call :log "[REPAIR][HIDDEN_IMPORT] PyInstaller rebuild failed; stopping recovery."
  set "HP_EXE_EXIT=1"
  goto :hidden_import_recover_done
)
goto :hidden_import_loop
:hidden_import_recover_done
if "%HP_EXE_EXIT%"=="0" if %HP_HIDDEN_ITER% GEQ 1 call :log "[REPAIR][HIDDEN_IMPORT] EXE verified after hidden-import recovery."
rem derived requirement: prior to this line, exhausting the 3-attempt cap with the EXE still
rem failing logged nothing beyond the per-iteration [REPAIR][HIDDEN_IMPORT] lines -- the user
rem only saw the generic post-build failure output, with no explicit signal that auto-recovery
rem was attempted and gave up.
if not "%HP_EXE_EXIT%"=="0" if %HP_HIDDEN_ITER% GEQ 3 call :log "[WARN][HIDDEN_IMPORT] Auto-recovery exhausted after 3 attempts; module(s) still missing."
if exist "dist\~exe_out.txt" del "dist\~exe_out.txt" >nul 2>&1
rem clean up artifacts created by recovery rebuilds (mirror the main-build cleanup);
rem preserve a user pre-existing spec.
if not defined HP_HID_SPEC_PRE if exist "%ENVNAME%.spec" del "%ENVNAME%.spec" >nul 2>&1
if exist "build\%ENVNAME%" rd /s /q "build\%ENVNAME%" >nul 2>&1
set "HP_HIDDEN_ITER="
set "HP_HIDDEN_TRIED="
set "HP_NEXT_HIDDEN="
set "HP_HID_SPEC_PRE="
rem CLAUDE.md Item 29: HP_PYI_HIDDEN_IMPORTS/HP_PYI_HID_COLLECT are deliberately NOT reset
rem here either (see this subroutine's own entry comment) -- a later :dll_bundle_recover call
rem in the SAME fresh build attempt needs to read whatever this call accumulated, to thread
rem it through its own rebuild command. Reset once per fresh build attempt instead, in
rem :run_entry_smoke.
exit /b 0
:warn_user_code_launch
rem REQ-016: tightly-scoped heads-up before a launch that can be force-stopped at ~30s, so the
rem user does not mistake a verification run for finished setup and start real work in it
rem (which would be lost when the run is killed). Called only where a 30s cap actually applies
rem -- the EXE smoke and hidden-import recovery -- not at the untimed entry smoke.
rem derived requirement: this is called for BOTH a normal PyInstaller build and an AV-Safe
rem Build Path Tier A fallback build (HP_NUITKA_FALLBACK_USED=1) -- the message previously
rem hardcoded "(PyInstaller)" even when the EXE being verified was actually Nuitka-built.
rem
rem derived requirement (docs/plan-cli-interactive-verification.md Open Question 1, owner
rem decision 2026-07-24): the main EXE smoke (~exe_smokerun.ps1, caller passes "main") no longer
rem unconditionally kills at 30s -- it only does so when the process has stayed completely
rem silent that whole time (see ~exe_smokerun.ps1's own header comment). The message must say so
rem accurately, or it would now overclaim a kill that may not happen. The hidden-import recovery
rem loop's own quick-check (caller passes "hidden_import") is UNCHANGED -- it still always kills
rem at 30s regardless of output, since its purpose is a bounded repair-verification check, not a
rem full run -- so it keeps the original, unconditional wording.
if "%~1"=="hidden_import" (
  if defined HP_NUITKA_FALLBACK_USED (
    call :log "[WARN] Verifying the built standalone EXE (fallback build system) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost."
  ) else (
    call :log "[WARN] Verifying the built standalone EXE (PyInstaller) now: it is force-stopped after about 30 seconds even if running perfectly, so do not start real work in it yet or any unsaved work will be lost."
  )
) else (
  if defined HP_NUITKA_FALLBACK_USED (
    call :log "[WARN] Verifying the built standalone EXE (fallback build system) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost."
  ) else (
    call :log "[WARN] Verifying the built standalone EXE (PyInstaller) now: if it stays completely silent for about 30 seconds it will be force-stopped, but any output (including a prompt waiting on your input) keeps it running as long as needed. If your program is interactive, try answering its prompts through to its own quit/exit option now so we can confirm it exits cleanly. Either way, do not start real work in it yet or any unsaved work will be lost."
  )
)
rem REQ-009/REQ-005.10 (cascade-vs-postexec fix): carry the "dependencies may be incomplete"
rem context (set in :warnfix_cascade_detect when a cascade was offered but not approved) into
rem this launch warning too, so a confusing runtime failure right after is not mistaken for a
rem bug in the user's own code.
if defined HP_DEP_MAYBE_INCOMPLETE call :log "[WARN] Note: dependency installation for this run may be incomplete (see the earlier warning above) -- unexpected behavior below could stem from that rather than a bug in your own code."
exit /b 0
:run_exe_smokerun
if not exist "dist\%ENVNAME%.exe" (
  call :log "[WARN] EXE smokerun: dist\%ENVNAME%.exe not found; skipping"
  exit /b 0
)
rem REQ-012: super-user hook -- skip EXE verification by request, BEFORE announcing a
rem test. Distinct from a verification failure, so HP_EXE_VERIFY_FAILED is NOT set; the
rem post-flight note records that the EXE was built but intentionally not run.
if defined HP_SKIP_EXE_SMOKERUN (
  call :log "[INFO] REQ-012: HP_SKIP_EXE_SMOKERUN set; skipping EXE verification (skipped by request)."
  set "HP_EXE_SKIPPED=1"
  exit /b 0
)
call :log "[INFO] EXE smokerun: testing dist\%ENVNAME%.exe"
call :log "[INFO] Running entry script smoke test via packaged EXE."
call :warn_user_code_launch main
set "HP_EXE_EXIT=-1"
rem CLAUDE.md Active Backlog Item 38 (resolved): the EXE smoke now runs from the app root (the
rem same current working directory (CWD) as :try_fast_exe/:verify_no_exe_interpreter and as the
rem interpreter's own run), not dist\ -- previously this was the ONE verification point using a
rem different CWD than every other one, so a CWD-relative-path app (e.g. open("config.json"))
rem could pass on a fresh build and fail on the very next (fast-path) run, or vice versa, with no
rem code change in between. Users place adjacent data files next to their .py source, which is the
rem app root, not a dist\ folder that does not even exist until after the build -- so app root is
rem the CWD that actually matches how a real double-clicked EXE finds its own adjacent files, not
rem just an artifact of how PyInstaller happens to lay out its output. Capture the EXE stdout/
rem stderr to ~run.out.txt / ~run.err.txt (now CWD-relative to the app root directly, no ..\
rem prefix needed) so the single EXE verification produces the run artifacts the old interpreter
rem smoke did -- the main 'smoke-ok' token is on stdout. Note: an app that writes a file next to
rem sys.argv[0] (e.g. the spaced-path test's ~smoke_token.txt) still writes it into dist\ for a
rem frozen EXE regardless of launch CWD (a separate, CWD-independent mechanism), so
rem selfapps_envsmoke.ps1 still reads that specific token from dist\.
rem
rem ~exe_smokerun.ps1 (HP_EXE_SMOKERUN) replaces the old inline -Command one-liner: it live-tees
rem the EXE's own stdout/stderr to the console as it arrives (docs/plan-cli-interactive-verification.md)
rem and is invoked DIRECTLY (no for /f/backtick stdout capture), reading its exit-code result back
rem from a static file instead -- capturing this script's own stdout would swallow the teed output
rem and corrupt result parsing (see that doc's Finding 6). Kill()-after-30s is unchanged.
rem CodeRabbit review, PR #470: resolve to an absolute path, matching :try_fast_exe_probe's own
rem defensive precedent -- .NET Process.Start's FileName resolution is a different mechanism from
rem cmd.exe's own relative launch, so keep this unambiguous rather than relying on the child
rem process inheriting the right CWD to resolve it, in case a future change alters how/where this
rem is invoked from.
set "HP_SMOKERUN_EXE=%CD%\dist\%ENVNAME%.exe"
rem [REQ-026]: the EXE is self-contained (no separate entry-file argv needed), so Arguments is
rem exactly the forwarded extra args, or empty -- HP_APP_ARGS is already a pre-quoted, ready
rem Windows Arguments string by construction.
set "HP_SMOKERUN_ARGS=%HP_APP_ARGS%"
set "HP_SMOKERUN_RESULT=~smokerun_result.txt"
if exist "~exe_smokerun.ps1" del "~exe_smokerun.ps1" >nul 2>&1
if exist "%HP_SMOKERUN_RESULT%" del "%HP_SMOKERUN_RESULT%" >nul 2>&1
call :emit_from_base64 "~exe_smokerun.ps1" HP_EXE_SMOKERUN
if errorlevel 1 (
  rem Extremely rare -- disk/permission failure writing a work file; HP_EXE_EXIT stays -1.
  call :log "[WARN] EXE smokerun: could not emit ~exe_smokerun.ps1; treating as a failed run."
) else (
  powershell -NoProfile -ExecutionPolicy Bypass -File "~exe_smokerun.ps1"
  if exist "%HP_SMOKERUN_RESULT%" (
    for /f "usebackq delims=" %%X in ("%HP_SMOKERUN_RESULT%") do set "HP_EXE_EXIT=%%X"
  )
  if exist "~exe_smokerun.ps1" del "~exe_smokerun.ps1" >nul 2>&1
  if exist "%HP_SMOKERUN_RESULT%" del "%HP_SMOKERUN_RESULT%" >nul 2>&1
)
set "HP_SMOKERUN_EXE="
set "HP_SMOKERUN_ARGS="
set "HP_SMOKERUN_RESULT="
if not defined HP_EXE_EXIT set "HP_EXE_EXIT=-1"
if "%HP_EXE_EXIT%"=="0" goto :smokerun_ok
call :log "[WARN] EXE smokerun: exited %HP_EXE_EXIT% (non-zero)"
rem Slice 2 (REQ-016): attempt strict --hidden-import auto-recovery before giving up.
rem Skip when HP_EXE_EXIT is -1 (a timeout/hang) -- re-running a hung EXE in the recovery
rem loop would hang too; only a real fast non-zero exit (e.g. ModuleNotFoundError) is fixable.
if not "%HP_EXE_EXIT%"=="-1" call :hidden_import_recover
rem CLAUDE.md Item 29 (CodeRabbit review finding on PR #421): deliberately no early
rem "if HP_EXE_EXIT==0 goto :smokerun_ok" here, unlike the check at line ~4454 above. A
rem hidden-import rebuild above (--collect-submodules=X) can pull in a package whose OWN
rem compiled extension needs a native DLL that was never checked -- the :dll_bundle_recover
rem call inside :run_entry_smoke only ever saw the ORIGINAL build's own warnings, before any
rem hidden-import rebuild ran. The rebuilt EXE can exit 0 on THIS smoke run's own code path
rem while the build log still shows a fresh, unactioned "Library not found" warning for a DLL a
rem DIFFERENT code path would need -- exactly the class of gap build-time detection exists to
rem catch before it becomes a guaranteed runtime failure (confirmed via a real pyproj/proj_9.dll
rem failure -- see docs/agent-interconnect.md's "Conda native-DLL bundling repair loop" section,
rem "Detects at BUILD time, not runtime"). So the block below must run regardless of whether this
rem rebuild already made the smoke run pass; it is gated on HP_HIDDEN_REPAIRED (below), not on
rem HP_EXE_EXIT -- if the call above did NOT actually rebuild anything, the log has not grown
rem since the FIRST :dll_bundle_recover call already scanned it, so there is nothing new to find,
rem and the gate skips the pointless re-scan in the common case instead of always paying for one.
if not "%HP_EXE_EXIT%"=="-1" if defined HP_HIDDEN_REPAIRED call :dll_bundle_recover
rem Only worth a fresh verification pass if this call actually bundled something -- the
rem common case (nothing new detected) must not pay for an extra EXE launch/wait.
if defined HP_DLL_REPAIRED (
  rem A DLL fix can also unblock a package whose OWN hidden-import gap was previously
  rem unreachable -- the DLL failure short-circuited the app before it got that far, e.g.
  rem colorama's own gap in self.layered_e2e.chain, only reached once pyproj's DLL is fixed.
  rem Give :hidden_import_recover one more bounded pass. Deliberately not chained further:
  rem each subroutine call already gets its own fresh, capped 3-iteration budget, and a
  rem THIRD round risks an unbounded repair cascade for a pathological dependency tree -- a
  rem case ever found needing more than this is its own future backlog item, not solved
  rem speculatively here. No -1 guard needed here: HP_DLL_REPAIRED can only be defined if
  rem the :dll_bundle_recover call just above actually ran, which itself required HP_EXE_EXIT
  rem to already be non--1, see the guard on that call -- and dll_bundle_recover never
  rem touches HP_EXE_EXIT, so that fact still holds at this point.
  call :hidden_import_recover
)
if "%HP_EXE_EXIT%"=="0" goto :smokerun_ok
rem REQ-016: record that the packaged EXE could not be verified so the post-flight
rem briefing can guide the user to run the app directly instead of claiming success.
set "HP_EXE_VERIFY_FAILED=1"
rem a -1 is a timeout/hang: no parseable error to hint on, and re-running the EXE in
rem :exe_smokerun_hints would hang too -- skip straight to the post-flight caveat.
if not "%HP_EXE_EXIT%"=="-1" call :exe_smokerun_hints
rem CLAUDE.md Active Backlog Item 41: ~exe_smokerun.ps1's own kill only fires when ZERO
rem stdout/stderr bytes were observed before the 30s deadline (its $sawOutput gate) -- exactly
rem the shape of a correctly-behaving GUI app (tkinter/PyQt, a mainloop with no console output),
rem not just a genuinely hung program. HP_EXE_EXIT=="-1" already implies this at this point in
rem the file, so no new runtime signal is needed -- just record it for the caveat panel below.
if "%HP_EXE_EXIT%"=="-1" set "HP_EXE_TIMEDOUT_SILENT=1"
goto :smokerun_ndjson
:smokerun_ok
call :log "[INFO] EXE smokerun: exited 0 (ok)"
set "HP_EXE_VERIFY_FAILED="
:smokerun_ndjson
rem REQ-018 (2b-A.2): unified verification vocabulary -- emit the same "Entry smoke exit=" line the
rem interpreter smoke used, so the single EXE verification satisfies the existing run assertions
rem (self.env.smoke.run / self.prime.run / self.prime.bootstrap) without re-pointing them.
call :log "[INFO] Entry smoke exit=%HP_EXE_EXIT%"
rem REQ-018 (2b-A): telemetry readout of the single verification run. HP_EXE_EXIT is the final
rem EXE exit after any hidden-import recovery: 0 = clean, -1 = the 30s cap was hit (force-stopped,
rem not necessarily broken), other = a real non-zero exit. This [STATUS] line is the readout the
rem 2b-C post-execution checkpoint (:run_postexec_checkpoint, called below) shows the user before
rem offering the elective second run. :log echoes unquoted, so keep the message free of
rem < > | & (parentheses are literal to echo outside if/for blocks).
if "%HP_EXE_EXIT%"=="0" (
  call :log "[STATUS] Run Status: SUCCESS (Exit Code: 0)"
) else if "%HP_EXE_EXIT%"=="-1" (
  call :log "[STATUS] Run Status: TIMED OUT (hit the ~30s verification cap; force-stopped, not necessarily broken)"
) else (
  call :log "[STATUS] Run Status: FAILED (Exit Code: %HP_EXE_EXIT%)"
)
if defined HP_NDJSON (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$c=[int]'%HP_EXE_EXIT%';" ^
    "$r=[ordered]@{id='self.exe.smokerun';pass=($c -eq 0);details=[ordered]@{exitCode=$c}}|ConvertTo-Json -Compress -Depth 8;" ^
    "Add-Content -Path '%HP_NDJSON%' -Value $r -Encoding ASCII" >> "%LOG%" 2>&1
)
rem REQ-009/REQ-005.10 (cascade-vs-postexec fix, CLAUDE.md docs/open-questions.md item 1):
rem when the user has already agreed to cascade to the next provider tier, this build is about
rem to be replaced -- do not also ask them to verify-again or optimize a build they just opted
rem away from. Both offers are purely elective (never required for correctness; the smoke run
rem above already happened regardless -- see the comment at its call site for why THAT must
rem never be skipped), so suppressing them here costs nothing if :provider_cascade later finds
rem every remaining tier unavailable and keeps this build after all -- the user was never asked
rem to verify/optimize it, but it was still verified once, just without the elective extras.
if defined HP_CASCADE_APPROVED (
  call :log "[INFO] REQ-009: cascade approved; skipping the post-verification offers for this build."
) else (
  call :run_postexec_checkpoint exe
  rem AV-Safe Build Path requirement 9, P1: offer an elective optimized build right after the
  rem verification telemetry above, while %HP_EXE_EXIT% still holds this run's real outcome --
  rem the next line clears it. See :offer_optimized_build's own header comment for the full gating.
  call :offer_optimized_build
)
set "HP_EXE_EXIT="
exit /b 0
:exe_smokerun_hints
rem derived requirement: re-run EXE briefly to capture stderr for pattern-based hints only.
rem Former Active Backlog item 15 (docs/agent-closed-backlog.md): a genuine ModuleNotFoundError/
rem FileNotFoundError exits immediately, but this is a FRESH re-run of the same binary, and any
rem non-determinism (a race, an env check that sometimes succeeds, anything that occasionally
rem blocks on inherited stdin) could hang this second invocation even though the FIRST one
rem legitimately classified as "fast, real, non-hang failure" -- this was the one user-code
rem launch point in this file with no timeout at all. Fixed via a dedicated bounded helper
rem (~exe_hint_rerun.ps1, HP_EXE_HINT_RERUN) with an UNCONDITIONAL kill deadline (not
rem activity-aware like ~exe_smokerun.ps1/~failfast_probe.ps1 -- this run is diagnostic-only,
rem never shown live, so partial output on a hang is fine and preferred over hanging the
rem bootstrap). Existing success/failure logic and the hint-matching below are unchanged.
rem CLAUDE.md Active Backlog Item 38 (resolved): this diagnostic re-run now shares the SAME
rem current working directory (CWD) as :run_exe_smokerun's own primary run (the app root, not
rem dist\) -- this rerun exists purely to explain why the primary run just failed, so it must
rem reproduce the exact same launch conditions the primary run used, or the hint could describe a
rem failure the primary run never actually hit (or miss one it did).
if not exist "dist\%ENVNAME%.exe" exit /b 0
if exist "~exe_hint_rerun.ps1" del "~exe_hint_rerun.ps1" >nul 2>&1
if exist "~exe_out.txt" del "~exe_out.txt" >nul 2>&1
call :emit_from_base64 "~exe_hint_rerun.ps1" HP_EXE_HINT_RERUN
if errorlevel 1 (
  rem Extremely rare -- disk/permission failure writing a work file; leave ~exe_out.txt absent
  rem so the findstr checks below simply find nothing and fall through to the generic hint.
  call :log "[WARN] EXE hint rerun: could not emit ~exe_hint_rerun.ps1; skipping hint capture."
) else (
  rem CodeRabbit review, PR #470: absolute path, same reasoning as :run_exe_smokerun's own
  rem HP_SMOKERUN_EXE assignment above.
  set "HP_HINT_RERUN_EXE=%CD%\dist\%ENVNAME%.exe"
  rem derived requirement: set explicitly rather than relying on the helper's own default --
  rem an inherited/leaked HP_HINT_RERUN_OUT from elsewhere in the environment would otherwise
  rem make the helper write somewhere other than what HP_HINT_FILE, below, reads and deletes.
  set "HP_HINT_RERUN_OUT=~exe_out.txt"
  powershell -NoProfile -ExecutionPolicy Bypass -File "~exe_hint_rerun.ps1"
  set "HP_HINT_RERUN_EXE="
  set "HP_HINT_RERUN_OUT="
  if exist "~exe_hint_rerun.ps1" del "~exe_hint_rerun.ps1" >nul 2>&1
)
set "HP_HINT_FILE=~exe_out.txt"
set "HP_HINT_FILE_NAME=<file>"
set "HP_HINT_MOD=<module>"
findstr /i /c:"FileNotFoundError" /c:"No such file or directory" "%HP_HINT_FILE%" >nul 2>&1
if not errorlevel 1 goto :hint_data_file
goto :hint_check_mod
:hint_data_file
for /f "usebackq delims=" %%F in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$t=[IO.File]::ReadAllText('%HP_HINT_FILE%');$m=[regex]::Match($t,'No such file or directory: ''([^'']+)''');if($m.Success){$m.Groups[1].Value}else{'<file>'}"`) do set "HP_HINT_FILE_NAME=%%F"
call :log "[HINT][DATA_FILE] Missing data file detected: %HP_HINT_FILE_NAME%"
rem derived requirement (CLAUDE.md Active Backlog Item 38): --add-data bundles a file into the
rem onefile extraction folder (_MEIxxxxxx under TEMP), never the current working directory --
rem correct advice only when the missing path itself is already under a _MEIxxxxxx folder (the
rem code reads it via a bundled-resource-style path). For a bare/CWD-relative path, --add-data
rem would NOT fix the failure at all; give honest advice for that far more common beginner case
rem instead of a suggestion that cannot work.
rem derived requirement (CodeRabbit review, PR #458): a plain findstr substring match on "_MEI"
rem false-positives on a coincidental filename (config_MEI.json) or a user-named folder with no
rem digits (C:\work\_MEI\data.txt) -- neither is a genuine PyInstaller extraction directory.
rem Matched via PowerShell instead, anchored to the real _MEIxxxxxx path-component shape
rem (a path separator, then _MEI, then a nonempty suffix, then a path separator) so a bare
rem substring never matches; the value is read from the environment at PowerShell runtime,
rem never substituted into the -Command text, so no cmd.exe metacharacter risk either.
rem derived requirement (real CI failure, PR #458's own mei_genuine test): the suffix is NOT
rem always decimal digits -- a genuine capture from this same PR's CI run showed
rem _MEI00001a4c2 (hex characters), not just _MEI41642 (decimal) from the earlier reference
rem capture. [0-9]+ missed the hex case and wrongly gave the CWD-relative advice for a
rem genuine extraction path. Widened to [^\\/]+ (any nonempty non-separator run) -- the
rem structural bounding (a real path separator immediately before AND after) is what
rem actually distinguishes a genuine extraction-directory component from a coincidental
rem substring or a no-suffix folder name, not the exact character class of the suffix.
powershell -NoProfile -ExecutionPolicy Bypass -Command "exit [int](-not ($env:HP_HINT_FILE_NAME -match '(?i)[\\/]_MEI[^\\/]+[\\/]'))"
if errorlevel 1 (
  call :log "[HINT][DATA_FILE] --add-data will not fix this: it bundles into a temp extraction folder, not the current directory. Place a copy of '%HP_HINT_FILE_NAME%' next to the built .exe instead, or read the file via a path relative to your own script file, not the working directory."
) else (
  call :log "[HINT][DATA_FILE] Consider adding: --add-data %HP_HINT_FILE_NAME%;."
)
if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='DATA_FILE';file=$env:HP_HINT_FILE_NAME}|ConvertTo-Json -Compress)"
:hint_check_mod
findstr /i /c:"ModuleNotFoundError" /c:"No module named" "%HP_HINT_FILE%" >nul 2>&1
if not errorlevel 1 goto :hint_hidden_import
goto :hint_packaging
:hint_hidden_import
for /f "usebackq delims=" %%M in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$t=[IO.File]::ReadAllText('%HP_HINT_FILE%');$m=[regex]::Match($t,'No module named ''([^'']+)''');if($m.Success){$m.Groups[1].Value}else{'<module>'}"`) do set "HP_HINT_MOD=%%M"
call :log "[HINT][HIDDEN_IMPORT] Hidden import likely missing: %HP_HINT_MOD%"
call :log "[HINT][HIDDEN_IMPORT] Consider adding: --hidden-import=%HP_HINT_MOD%"
if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='HIDDEN_IMPORT';module=$env:HP_HINT_MOD}|ConvertTo-Json -Compress)"
:hint_packaging
call :log "[HINT][RUNTIME_MISMATCH] Standalone EXE behavior differs from the Python runtime (possible PyInstaller packaging issue in the EXE, not your environment or dependencies)"
if defined HINT_JSON powershell -NoProfile -ExecutionPolicy Bypass -Command "Write-Host ([PSCustomObject]@{hint_type='RUNTIME_MISMATCH'}|ConvertTo-Json -Compress)"
if exist "%HP_HINT_FILE%" del "%HP_HINT_FILE%" >nul 2>&1
set "HP_HINT_FILE="
set "HP_HINT_FILE_NAME="
set "HP_HINT_MOD="
exit /b 0
:write_pipreqs_summary
if "%HP_JOB_SUMMARY%"=="" exit /b 0
set "HP_SUMMARY_PATH=%HP_JOB_SUMMARY%"
if not defined HP_PIPREQS_SUMMARY_PHASE set "HP_PIPREQS_SUMMARY_PHASE=<unknown>"
> "%HP_SUMMARY_PATH%" echo Interpreter: %HP_PY%
>> "%HP_SUMMARY_PATH%" echo Pipreqs command: pipreqs . --force --mode compat --savepath "%HP_PIPREQS_SUMMARY_CMD_PATH%"%HP_PIPREQS_SUMMARY_IGNORE%
if defined HP_PIPREQS_SUMMARY_NOTE (
  >> "%HP_SUMMARY_PATH%" echo Phase: %HP_PIPREQS_SUMMARY_PHASE% %HP_PIPREQS_SUMMARY_NOTE%
) else (
  >> "%HP_SUMMARY_PATH%" echo Phase: %HP_PIPREQS_SUMMARY_PHASE%
)
if defined HP_PIPREQS_FAILURE_LOG (
  powershell -NoProfile -ExecutionPolicy Bypass -Command ^
    "$summary = '%HP_SUMMARY_PATH%'; $logPath = '%HP_PIPREQS_FAILURE_LOG%';" ^
    "Add-Content -Path $summary -Value '---' -Encoding ASCII;" ^
    "Add-Content -Path $summary -Value 'Pipreqs log tail:' -Encoding ASCII;" ^
    "if (Test-Path $logPath) {" ^
    "  Get-Content -LiteralPath $logPath -Tail 20 | Out-File -FilePath $summary -Encoding ASCII -Append;" ^
    "} else {" ^
    "  Add-Content -Path $summary -Value '<log tail unavailable>' -Encoding ASCII;" ^
    "}" >> "%LOG%" 2>&1
)
exit /b 0

:write_status
set "HP_STATE=%~1"
set "HP_EXIT=%~2"
set "HP_PYFILES=%~3"
if "%HP_STATE%"=="" set "HP_STATE=error"
if "%HP_EXIT%"=="" set "HP_EXIT=0"
if "%HP_PYFILES%"=="" set "HP_PYFILES=%PYCOUNT%"
echo {"state":"%HP_STATE%","exitCode":%HP_EXIT%,"pyFiles":%HP_PYFILES%}> "%STATUS_FILE%"
if exist "%STATUS_FILE%" (
  if exist "~bootstrap.status.txt" del "~bootstrap.status.txt" >nul 2>&1
) else if /i "%HP_STATE%"=="no_python_files" (
  > "~bootstrap.status.txt" echo [INFO] Python file count: %HP_PYFILES%
  >> "~bootstrap.status.txt" echo [INFO] No Python files detected; skipping environment bootstrap.
)
set "HP_STATE="
set "HP_EXIT="
set "HP_PYFILES="
exit /b 0
:emit_from_base64
rem Decode helper payloads with PowerShell Convert.FromBase64String (see https://learn.microsoft.com/dotnet/api/system.convert.frombase64string).
rem Keep these helpers in sync with README.md regeneration notes.
set "DST=%~1"
set "VAR=%~2"
if not defined DST exit /b 1
if not defined VAR exit /b 1
rem derived requirement: CLAUDE.md Item 46 Bucket A Batch regression coverage -- deterministically
rem force a SPECIFIC payload's own write to fail (simulating the disk-write/AV-lock class of
rem failure every real call site of this subroutine can hit) without touching any other payload
rem in the same run. Absence changes nothing (REQ-019): only fires when HP_TEST_FORCE_EMIT_FAIL
rem exactly matches this call's own %VAR%.
if defined HP_TEST_FORCE_EMIT_FAIL if /i "%HP_TEST_FORCE_EMIT_FAIL%"=="%VAR%" (
  call :log "[TEST] HP_TEST_FORCE_EMIT_FAIL=%VAR%: simulating embedded helper write failure."
  exit /b 1
)
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
  "$varName = '%VAR%'; $envItem = Get-Item Env:$varName -ErrorAction SilentlyContinue; if (-not $envItem) { exit 1 }; $base64 = $envItem.Value; if (-not $base64) { exit 1 }; $outFile = Join-Path (Get-Location) '%DST%'; $bytes = [Convert]::FromBase64String($base64); [IO.File]::WriteAllBytes($outFile, $bytes)" >> "%LOG%" 2>&1
exit /b %errorlevel%
:define_helper_payloads
rem Helper payloads are base64-encoded so run_setup.bat stays self-contained.
rem Regenerate with python - <<'PY' snippets as noted in README.md (base64 docs: https://docs.python.org/3/library/base64.html).
set " "
set "HP_CONDARC=Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpjaGFubmVsX3ByaW9yaXR5OiBzdHJpY3QKc2hvd19jaGFubmVsX3VybHM6IHRydWUK"
set " "
set "HP_PRINT_PYVER=aW1wb3J0IHN5cwoKcHJpbnQoZiJweXRob24te3N5cy52ZXJzaW9uX2luZm9bMF19LntzeXMudmVyc2lvbl9pbmZvWzFdfS57c3lzLnZlcnNpb25faW5mb1syXX0iKQo="
rem HP_FAST_CHECK decoded content (CLAUDE.md Active Backlog Item 39): content-hash freshness
rem check over the same *.py file set (unchanged filter), extended to requirements.txt/
rem pyproject.toml/runtime.txt. Two modes via the second positional arg: 'check' (default,
rem called from :try_fast_exe) prints 'fresh' iff a stored ~fast_check.hash.txt exists and
rem matches the current source hash; 'write' (called from :success) unconditionally
rem (re)writes it. See tools/fast_check.ps1 (the canonical source) for the full script and
rem docs/agent-lessons-learned.md's "Prefer raw .NET types over Utility-module cmdlets"
rem entry for why this uses [System.Security.Cryptography.SHA256] directly, not Get-FileHash.
rem If HP_FAST_CHECK changes, update this decoded comment block to match the base64 payload.
set " "
set " "
set " "
set " "
set " "
set " "
set " "
:: --- Embedded helper: HP_PREP_REQUIREMENTS (~prep_requirements.py) ---
:: Purpose:
::   - Normalize pip/conda specifiers from requirements.txt
::   - Apply heuristic extras (REQ-005.8)
::
:: Current heuristics applied (set HP_DISABLE_HEURISTICS=1 to skip):
::   REQ-005.8.1 -- pandas  -> openpyxl (+ xlsxwriter)
::   REQ-005.8.2 -- requests -> certifi
::   REQ-005.8.3 -- sqlalchemy -> pymysql
::   REQ-005.8.4 -- matplotlib -> tk
::   REQ-005.8.5 -- cryptography/pycryptodome -> cffi
::
:: Log format: [HEURISTIC] <source->target>  (emitted to stderr -> ~setup.log)
:: Tests: selfapps_pandas_excel.ps1 validates REQ-005.8.1 translation
:: --------------------------------------------------------------------
set " "
set "HP_DETECT_VISA=IiIiZGV0ZWN0X3Zpc2EgKEhQX0RFVEVDVF9WSVNBKSAtLSBzY2FucyB0aGUgcHJvamVjdCBkaXJlY3RvcnkgZm9yCk5JLVZJU0EtaW5zdHJ1bWVudC1jb250cm9sIGltcG9ydHMgKHB5dmlzYSkgdG8gZGVjaWRlIHdoZXRoZXIgdGhlIFJFUS0wMDgKTkktVklTQSBkcml2ZXIgaW5zdGFsbCBicmFuY2ggc2hvdWxkIHJ1bi4KClJ1biBmcm9tIHRoZSBhcHBsaWNhdGlvbiBkaXJlY3Rvcnk7IHJlY3Vyc2l2ZWx5IHdhbGtzIHRoZSBjdXJyZW50IGRpcmVjdG9yeQooc2tpcHBpbmcgIn4iLyIuIi1wcmVmaXhlZCBzdWJkaXJlY3RvcmllcyBhbmQgZmlsZXMsIG1hdGNoaW5nIHRoaXMgcmVwbydzCnVzdWFsIHNjcmF0Y2gtZmlsZSBjb252ZW50aW9uKSBhbmQgcHJpbnRzICIxIiB0byBzdGRvdXQgKG5vIHRyYWlsaW5nCm5ld2xpbmUpIGlmIGFueSBzY2FubmVkIC5weSBmaWxlJ3Mgc291cmNlIG1hdGNoZXMgYSBweXZpc2Etc3R5bGUgaW1wb3J0LAplbHNlICIwIi4KClBBVFRFUk5TIGFuY2hvciBvbiB0aGUgZnVsbCBtb2R1bGUgbmFtZSAoInB5dmlzYSIvInZpc2EiKSB3aXRoIGEgdHJhaWxpbmcKXGIsIG5vdCBhIHRydW5jYXRlZCAicHl2aXMiLyJ2aXMiIHByZWZpeCAtLSB0aGlzIGRlbGliZXJhdGVseSBleGNsdWRlcyBhbgp1bnJlbGF0ZWQgaW1wb3J0IHRoYXQgbWVyZWx5IFNUQVJUUyB3aXRoIHRob3NlIGxldHRlcnMgKGUuZy4gImltcG9ydApweXZpc3RhIiwgYSByZWFsLCBwb3B1bGFyLCB1bnJlbGF0ZWQgM0QtdmlzdWFsaXphdGlvbiBwYWNrYWdlOyAiaW1wb3J0CnZpc2lvbiI7ICJpbXBvcnQgcHl2aXNjb2VsYXN0aWMiKS4gXGIgYWZ0ZXIgdGhlIGZ1bGwgd29yZCBhbHNvIGFsbG93cyBhCmRvdHRlZCBzdWJtb2R1bGUgaW1wb3J0ICgiaW1wb3J0IHB5dmlzYS5jb25zdGFudHMiKSB0byBzdGlsbCBtYXRjaCwgc2luY2UKIi4iIGlzIGEgbm9uLXdvcmQgY2hhcmFjdGVyLiBOb3RlOiBhbmNob3JpbmcgXGIgcmlnaHQgYWZ0ZXIgdGhlIHRydW5jYXRlZAoidmlzIi8icHl2aXMiIHByZWZpeCAocmF0aGVyIHRoYW4gdGhlIGZ1bGwgd29yZCkgd291bGQgTk9UIHdvcmsgaGVyZSAtLSBcYgpvbmx5IG1hdGNoZXMgYXQgYSB3b3JkL25vbi13b3JkIHRyYW5zaXRpb24sIGFuZCB0aGVyZSBpcyBubyBzdWNoIGJvdW5kYXJ5CmJldHdlZW4gInZpcyIgYW5kIHRoZSAiYSIgaW4gInZpc2EiIChib3RoIGFyZSB3b3JkIGNoYXJhY3RlcnMpLCBzbyBhCiJ2aXNcYiIgcGF0dGVybiB3b3VsZCBmYWlsIHRvIG1hdGNoIHRoZSBnZW51aW5lICJpbXBvcnQgdmlzYSIgY2FzZS4KClRoaXMgaXMgdGhlIGNhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9ERVRFQ1RfVklTQSBiYXNlNjQgcGF5bG9hZCBlbWJlZGRlZAppbiBydW5fc2V0dXAuYmF0LiBBZnRlciBlZGl0aW5nLCByZS1lbmNvZGUgYW5kIHBhc3RlIGl0IGludG8gdGhlCmBzZXQgIkhQX0RFVEVDVF9WSVNBPS4uLiJgIGxpbmU7IHRlc3RzL3Rlc3RfZGV0ZWN0X3Zpc2EucHkgYXNzZXJ0cyB0aGUKZW1iZWRkZWQgcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZS4KIiIiCmltcG9ydCBvcywgcmUsIHN5cwoKUk9PVCA9IG9zLmdldGN3ZCgpClBBVFRFUk5TID0gWwogICAgciIoP20pXlxzKig/OmZyb21ccytweXZpc2FcYnxpbXBvcnRccytweXZpc2FcYikiLAogICAgciIoP20pXlxzKmltcG9ydFxzK3Zpc2FcYiIsCl0KCmRlZiBuZWVkc192aXNhKCk6CiAgICBmb3IgY3VycmVudCwgZGlycywgZmlsZXMgaW4gb3Mud2FsayhST09UKToKICAgICAgICBkaXJzWzpdID0gW2l0ZW0gZm9yIGl0ZW0gaW4gZGlycyBpZiBub3QgaXRlbS5zdGFydHN3aXRoKCgnficsICcuJykpXQogICAgICAgIGZvciBuYW1lIGluIGZpbGVzOgogICAgICAgICAgICBpZiBub3QgbmFtZS5lbmRzd2l0aCgnLnB5Jykgb3IgbmFtZS5zdGFydHN3aXRoKCd+Jyk6CiAgICAgICAgICAgICAgICBjb250aW51ZQogICAgICAgICAgICBwYXRoID0gb3MucGF0aC5qb2luKGN1cnJlbnQsIG5hbWUpCiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihwYXRoLCAncicsIGVuY29kaW5nPSd1dGYtOCcsIGVycm9ycz0naWdub3JlJykgYXMgaGFuZGxlOgogICAgICAgICAgICAgICAgICAgIHRleHQgPSBoYW5kbGUucmVhZCgpCiAgICAgICAgICAgIGV4Y2VwdCBPU0Vycm9yOgogICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgZm9yIHBhdHRlcm4gaW4gUEFUVEVSTlM6CiAgICAgICAgICAgICAgICBpZiByZS5zZWFyY2gocGF0dGVybiwgdGV4dCk6CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgIHJldHVybiBGYWxzZQoKZGVmIG1haW4oKToKICAgIHN5cy5zdGRvdXQud3JpdGUoJzEnIGlmIG5lZWRzX3Zpc2EoKSBlbHNlICcwJykKCmlmIF9fbmFtZV9fID09ICdfX21haW5fXyc6CiAgICBtYWluKCkK"
rem ~find_entry.py emits a normalized crumb, logs it for tests, and skip mode reads its stdout
set " "
rem ~env_state.py records envMode/envName/envPath/lockSize after a successful
rem conda bootstrap; prints 'skip' on --check when the env is still valid,
rem 'run' otherwise. Writes ~env.state.json on --write.
set "HP_ENV_STATE=IiIiZW52X3N0YXRlIHYzICgyMDI2LTAzLTI3KQpXcml0ZXMgYW5kIHZhbGlkYXRlcyB+ZW52LnN0YXRlLmpzb24gZm9yIHRoZSBydW5fc2V0dXAuYmF0IGJvb3RzdHJhcCBmYXN0IHBhdGguClVzYWdlOgogIHB5dGhvbiB+ZW52X3N0YXRlLnB5IC0tY2hlY2sgIDogcHJpbnQgJ3NraXAnIGlmIHRoZSBzYXZlZCBlbnYgc3RhdGUgaXMgc3RpbGwgdmFsaWQKICBweXRob24gfmVudl9zdGF0ZS5weSAtLXdyaXRlICA6IHdyaXRlIGN1cnJlbnQgZW52IHN0YXRlIHRvIH5lbnYuc3RhdGUuanNvbgpOb3RlOiBweVNwZWMgaXMgaW50ZW50aW9uYWxseSBvbWl0dGVkIGZyb20gdGhlIHN0YXRlIGNoZWNrIGJlY2F1c2UgcnVuX3NldHVwLmJhdAp3cml0ZXMgcnVudGltZS50eHQgKmR1cmluZyogdGhlIGZpcnN0IGJvb3RzdHJhcCwgc28gdGhlIGRldGVjdGVkIHNwZWMgb24gcnVuIDIKZGlmZmVycyBmcm9tIHRoZSBlbXB0eSBzcGVjIG9uIHJ1biAxLCBjYXVzaW5nIGEgc3B1cmlvdXMgY2FjaGUgbWlzcy4KClRoaXMgaXMgdGhlIGNhbm9uaWNhbCBzb3VyY2UgZm9yIHRoZSBIUF9FTlZfU1RBVEUgYmFzZTY0IHBheWxvYWQgZW1iZWRkZWQgaW4KcnVuX3NldHVwLmJhdC4gQWZ0ZXIgZWRpdGluZywgcmUtZW5jb2RlIGFuZCBwYXN0ZSBpdCBpbnRvIHRoZQpgc2V0ICJIUF9FTlZfU1RBVEU9Li4uImAgbGluZTsgdGVzdHMvdGVzdF9lbnZfc3RhdGUucHkgYXNzZXJ0cyB0aGUgZW1iZWRkZWQKcGF5bG9hZCBtYXRjaGVzIHRoaXMgZmlsZS4KIiIiCl9fdmVyc2lvbl9fID0gImVudl9zdGF0ZSB2MyAoMjAyNi0wMy0yNykiCl9fYWxsX18gPSBbInJlYWRfc3RhdGUiLCAid3JpdGVfc3RhdGUiLCAiY2hlY2tfc3RhdGUiXQoKaW1wb3J0IGpzb24KaW1wb3J0IG9zCmltcG9ydCBzeXMKClNUQVRFX0ZJTEUgPSAifmVudi5zdGF0ZS5qc29uIgpMT0NLX0ZJTEUgPSAifmVudmlyb25tZW50LmxvY2sudHh0IgoKCmRlZiBfbG9ja19zaXplKCk6CiAgICB0cnk6CiAgICAgICAgcmV0dXJuIG9zLnBhdGguZ2V0c2l6ZShMT0NLX0ZJTEUpCiAgICBleGNlcHQgT1NFcnJvcjoKICAgICAgICByZXR1cm4gMAoKCmRlZiByZWFkX3N0YXRlKCk6CiAgICB0cnk6CiAgICAgICAgd2l0aCBvcGVuKFNUQVRFX0ZJTEUsICJyIiwgZW5jb2Rpbmc9InV0Zi04IiwgZXJyb3JzPSJpZ25vcmUiKSBhcyBmaDoKICAgICAgICAgICAgcmV0dXJuIGpzb24ubG9hZChmaCkKICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgcmV0dXJuIHt9CgoKZGVmIHdyaXRlX3N0YXRlKCk6CiAgICBlbnZfbW9kZSA9IG9zLmVudmlyb24uZ2V0KCJIUF9FTlZfTU9ERSIsICIiKQogICAgZW52X25hbWUgPSBvcy5lbnZpcm9uLmdldCgiRU5WTkFNRSIsICIiKQogICAgZW52X3BhdGggPSBvcy5lbnZpcm9uLmdldCgiRU5WX1BBVEgiLCAiIikKICAgIGxvY2tfc2l6ZSA9IF9sb2NrX3NpemUoKQogICAgc3RhdGUgPSB7CiAgICAgICAgImVudk1vZGUiOiBlbnZfbW9kZSwKICAgICAgICAiZW52TmFtZSI6IGVudl9uYW1lLAogICAgICAgICJlbnZQYXRoIjogZW52X3BhdGgsCiAgICAgICAgImxvY2tTaXplIjogbG9ja19zaXplLAogICAgfQogICAgdHJ5OgogICAgICAgIHdpdGggb3BlbihTVEFURV9GSUxFLCAidyIsIGVuY29kaW5nPSJ1dGYtOCIpIGFzIGZoOgogICAgICAgICAgICBqc29uLmR1bXAoc3RhdGUsIGZoKQogICAgZXhjZXB0IE9TRXJyb3I6CiAgICAgICAgc3lzLmV4aXQoMSkKCgpkZWYgY2hlY2tfc3RhdGUoKToKICAgIHN0YXRlID0gcmVhZF9zdGF0ZSgpCiAgICBpZiBub3Qgc3RhdGU6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgZW52X25hbWUgPSBvcy5lbnZpcm9uLmdldCgiRU5WTkFNRSIsICIiKQogICAgaWYgbm90IGVudl9uYW1lIG9yIHN0YXRlLmdldCgiZW52TmFtZSIpICE9IGVudl9uYW1lOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKICAgICAgICByZXR1cm4KICAgIGlmIHN0YXRlLmdldCgiZW52TW9kZSIpICE9ICJjb25kYSI6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgZW52X3BhdGggPSBzdGF0ZS5nZXQoImVudlBhdGgiLCAiIikKICAgIGlmIG5vdCBlbnZfcGF0aDoKICAgICAgICBzeXMuc3Rkb3V0LndyaXRlKCJydW5cbiIpCiAgICAgICAgcmV0dXJuCiAgICBweV9leGUgPSBvcy5wYXRoLmpvaW4oZW52X3BhdGgsICJweXRob24uZXhlIikKICAgIGlmIG5vdCBvcy5wYXRoLmV4aXN0cyhweV9leGUpOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKICAgICAgICByZXR1cm4KICAgIGxvY2tfc2l6ZSA9IF9sb2NrX3NpemUoKQogICAgaWYgbG9ja19zaXplID09IDAgb3IgbG9ja19zaXplICE9IHN0YXRlLmdldCgibG9ja1NpemUiLCAtMSk6CiAgICAgICAgc3lzLnN0ZG91dC53cml0ZSgicnVuXG4iKQogICAgICAgIHJldHVybgogICAgc3lzLnN0ZG91dC53cml0ZSgic2tpcFxuIikKCgpkZWYgbWFpbigpOgogICAgYXJncyA9IHN5cy5hcmd2WzE6XQogICAgaWYgIi0td3JpdGUiIGluIGFyZ3M6CiAgICAgICAgd3JpdGVfc3RhdGUoKQogICAgZWxpZiAiLS1jaGVjayIgaW4gYXJnczoKICAgICAgICBjaGVja19zdGF0ZSgpCiAgICBlbHNlOgogICAgICAgIHN5cy5zdGRvdXQud3JpdGUoInJ1blxuIikKCgppZiBfX25hbWVfXyA9PSAiX19tYWluX18iOgogICAgbWFpbigpCg=="
rem ~parse_warn.py reads PyInstaller warn file, extracts missing module names,
rem applies import-to-conda-package translations, and prints one package per line.
set " "
rem ~dep_check.py compares requirements.auto.txt against ~environment.lock.txt;
rem prints 'skip' when all pipreqs packages are already installed, 'run' otherwise.
set " "
rem ~autopep_merge.py (Tier 1, docs/plan-autopep723-two-tier.md) merges
rem autopep723 check discoveries into requirements.txt, additive/best-effort only.
set " "
rem ~pvw_known_idempotent.py (Tier 2, docs/plan-autopep723-two-tier.md,
rem HP_PVW_KNOWN_IDEMPOTENT) runs the entry script via uvx autopep723 for
rem execute-mode dependency discovery; opt-in only.
set " "
rem ~collect_submodules.py emits pre-build --collect-submodules flags for packages
rem that load submodules dynamically; double-gated on used-by-source AND installed.
set " "
rem ~hidden_import_scan.py decides the next --hidden-import target from a frozen EXE
rem stderr: strict ModuleNotFoundError + the module must be installed in the build env.
set " "
rem ~dll_pct_sanitize.ps1 strips %/^ from env var values for :log's UNQUOTED-echo safety --
rem emitted as a real .ps1 file (not inline -Command text) so cmd.exe never parses its content.
set "HP_DLL_PCT_SANITIZE=IyBTdHJpcHMgJS9eIGZyb20gb25lIG9yIG1vcmUgZW52IHZhciB2YWx1ZXMsIGZvciA6bG9nJ3MgVU5RVU9URUQtZWNobyBzYWZldHkgKHNlZQojIGRvY3MvYWdlbnQtbGVzc29ucy1sZWFybmVkLm1kJ3MgIjpsb2cgZWNob2VzIFVOUVVPVEVEIiBlbnRyeSkuIEEgbGl0ZXJhbCAlIG9uIHRoZSBzYW1lIGNtZC5leGUKIyBsb2dpY2FsIGxpbmUgYXMgYSByZWFsICVWQVIlIHJlZmVyZW5jZSAoZS5nLiAlTE9HJSkgY2FuIHNpbGVudGx5IGNvcnJ1cHQgY21kLmV4ZSdzIG93bgojIGxlZnQtdG8tcmlnaHQgJS1wYWlyaW5nIHNjYW4gLS0gdGhpcyBleGFjdCBoYXphcmQgYnJva2UgdGhlIG9sZCBpbmxpbmUgYC1Db21tYW5kYCB2ZXJzaW9uIG9mCiMgdGhpcyBsb2dpYyBUSFJFRSB0aW1lcyBpbiByZWFsIENJIGJlZm9yZSBsYW5kaW5nIGhlcmUuIEVtaXR0ZWQgYXMgYSByZWFsIC5wczEgZmlsZSBzcGVjaWZpY2FsbHkKIyBzbyB0aGlzIHRleHQgaXMgTkVWRVIgcGFyc2VkIGJ5IGNtZC5leGUncyBvd24gdG9rZW5pemVyIC0tIG9ubHkgdGhlIG91dGVyIGAtRmlsZSAicGF0aCIgYXJnLi4uYAojIGxpbmUgaXMsIGFuZCBwbGFpbiBhcmd2IGhhcyBubyAlL14tcGFpcmluZyBoYXphcmQuICYvfC88Lz4gYXJlIGhhbmRsZWQgYnkgdGhlIENBTExFUiB2aWEgcGxhaW4KIyBjbWQuZXhlIDpzZWFyY2g9cmVwbGFjZSBzdWJzdGl0dXRpb24gYmVmb3JlIHRoaXMgcnVucyAodGhhdCBwYXJ0IHdhcyBuZXZlciBicm9rZW4pLgojCiMgVXNhZ2U6IHBvd2Vyc2hlbGwgLUZpbGUgZGxsX3BjdF9zYW5pdGl6ZS5wczEgPGVudlZhck5hbWU+IDxvdXRGaWxlPiBbPGVudlZhck5hbWU+IDxvdXRGaWxlPiAuLi5dCiMgRm9yIGVhY2ggcGFpciwgcmVhZHMgW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZShuYW1lKSwgc3RyaXBzICUvXiwgYW5kIHdyaXRlcyB0aGUKIyByZXN1bHQgKG5vIHRyYWlsaW5nIG5ld2xpbmUpIHRvIG91dEZpbGUgLS0gcmVhZCBiYWNrIGJ5IHRoZSBjYWxsZXIgdmlhIGEgcGxhaW4KIyAiZm9yIC9mICJ1c2ViYWNrcSBkZWxpbXM9IiAlJVggaW4gKG91dEZpbGUpIGRvIHNldCIuCiMKIyBDYW5vbmljYWwgc291cmNlIGZvciB0aGUgSFBfRExMX1BDVF9TQU5JVElaRSBwYXlsb2FkIGluIHJ1bl9zZXR1cC5iYXQuIEFmdGVyIGVkaXRpbmc6CiMgYHB5dGhvbiB0b29scy9zeW5jX3BheWxvYWQucHkgSFBfRExMX1BDVF9TQU5JVElaRSB0b29scy9kbGxfcGN0X3Nhbml0aXplLnBzMWAuCiMgdGVzdHMvdGVzdF9kbGxfcGN0X3Nhbml0aXplLnB5IGFzc2VydHMgdGhlIGVtYmVkZGVkIHBheWxvYWQgbWF0Y2hlcyAoQ1JMRi9MRiBub3JtYWxpemVkKS4KJHBjdCA9IFtjaGFyXTM3CmZvciAoJGkgPSAwOyAkaSAtbHQgJGFyZ3MuQ291bnQ7ICRpICs9IDIpIHsKICAgICRuYW1lID0gJGFyZ3NbJGldCiAgICAkb3V0UGF0aCA9ICRhcmdzWyRpICsgMV0KICAgICR2ID0gW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZSgkbmFtZSkKICAgIGlmICgtbm90ICR2KSB7ICR2ID0gJycgfQogICAgJHYgPSAkdiAtcmVwbGFjZSAkcGN0LCAnXycgLXJlcGxhY2UgJ1xeJywgJ18nCiAgICBbU3lzdGVtLklPLkZpbGVdOjpXcml0ZUFsbFRleHQoJG91dFBhdGgsICR2LCBbU3lzdGVtLlRleHQuRW5jb2RpbmddOjpBU0NJSSkKfQo="
set " "
set " "
rem ~pep723_writeback.py promotes resolved dependencies into the entry file's
rem PEP 723 header via uv add --script; see docs/plan-pep723-writeback.md Part 2.1.
set " "
exit /b 0
:log
rem derived requirement (CLAUDE.md Item 42, lever 1): DEBUG/TRACE/INSTALL-tagged lines are
rem suppressed from the LIVE console by default (opt back in via HP_VERBOSE_CONSOLE=1) -- the
rem full detail is always written to LOG regardless, so this only trims what a beginner
rem double-click user sees, never what a diagnostic re-read of ~setup.log can find. Detected
rem via a pure substring-slice comparison (VAR colon-tilde start,length), never findstr/piping --
rem see docs/agent-lessons-learned.md's "Quote a variable before piping it into findstr" entry
rem for why a subprocess pipe on MSG would be hazardous here (message text can legally contain
rem "&"). HP_VERBOSE_CONSOLE is checked against the exact string "1", not merely defined, so an
rem accidental HP_VERBOSE_CONSOLE=0 (or any other non-"1" value) still suppresses -- matches this
rem file's own established flag-check idiom elsewhere (e.g. HP_FORCE_CONDA_ONLY).
set "MSG=%~1"
set "HP_LOG_SUPPRESS="
if not "%HP_VERBOSE_CONSOLE%"=="1" (
  if "%MSG:~0,7%"=="[DEBUG]" set "HP_LOG_SUPPRESS=1"
  if "%MSG:~0,7%"=="[TRACE]" set "HP_LOG_SUPPRESS=1"
  if "%MSG:~0,9%"=="[INSTALL]" set "HP_LOG_SUPPRESS=1"
)
if not defined HP_LOG_SUPPRESS echo %date% %time% %MSG%
>> "%LOG%" echo [%date% %time%] %MSG%
exit /b 0
:write_runtime_txt
rem derived requirement: called from inside a parenthesized if-block so %PYVER%
rem would expand at block-parse time (empty) if inlined. Subroutine body is
rem re-parsed at call time, so %PYVER% correctly reflects the for/f result.
rem derived requirement: guarded by HP_RUNTIME_TXT_PREEXIST so write-back only
rem fires when runtime.txt did not pre-exist (Tier 2/3 promotion to Tier 1).
if not defined HP_RUNTIME_TXT_PREEXIST if not "%PYVER%"=="" (
  >"runtime.txt" echo %PYVER%
  if errorlevel 1 (
    call :log "[WARN] runtime.txt write failed (read-only filesystem?). Tier 3 remains active."
  ) else (
    call :log "[INFO] runtime.txt written: %PYVER%"
    set "HP_PYSPEC_ORIGINAL=%PYSPEC%"
    set "PYSPEC=%PYVER:python-=python=%"
    rem derived requirement: this PYSPEC value is an artifact of whichever provider
    rem happened to run first, not a genuine user requirement -- see the identical
    rem comment at :try_conda_create's PYSPEC decision for why this must not be
    rem forced onto a LATER, different provider during a REQ-009 cascade re-entry.
    rem HP_PYSPEC_ORIGINAL preserves whatever constraint -- a genuine pyproject.toml/
    rem PEP 723 range, or nothing -- existed immediately before this overwrite.
    set "HP_PYSPEC_WRITEBACK=1"
  )
)
exit /b 0
rem :conda_binary_corrupt -- REQ-020: shows user-friendly message when conda binary fails health check.
rem Called when: (a) HP_TEST_CORRUPT_CONDA=1, (b) call "%CONDA_BAT%" info returns non-zero (DLL error, etc.).
rem Interactive users get a Y/N prompt to self-heal; CI exits immediately.
rem HP_TEST_HEAL_ANSWER bypasses HP_CI_LANE gate so CI can test the decline path without pausing.
rem PVW_CONDA_EXE overrides skip self-heal: we must not delete a user-managed conda root.
:conda_binary_corrupt
cls
echo.
echo ================================================================
echo   CORRUPTED PYTHON ENVIRONMENT DETECTED
echo ================================================================
echo.
echo   The local conda installation appears to be broken.
echo   This can happen after a Windows update or OS migration
echo   ^(example: DLL load error 0xc000007b^).
echo.
echo   Affected path: %MINICONDA_ROOT%
echo.
call :log "[ERROR] Corrupt conda binary detected at: %CONDA_BAT%"
if defined PVW_CONDA_EXE goto :corrupt_override_exit
if defined HP_TEST_HEAL_ANSWER goto :heal_prompt
if defined HP_CI_LANE goto :corrupt_ci_exit
:heal_prompt
set "HP_HEAL_RAW="
if defined HP_TEST_HEAL_ANSWER (
  set "HP_HEAL_RAW=%HP_TEST_HEAL_ANSWER%"
) else (
  set /p "HP_HEAL_RAW=  Would you like to delete it and rebuild? [Y/N] "
)
set "HP_HEAL_CHOICE=%HP_HEAL_RAW:~0,1%"
if /I "%HP_HEAL_CHOICE%"=="Y" goto :evict_and_rebuild
echo.
echo   Exiting without changes. Delete the folder above manually,
echo   then run this setup again.
echo.
call :die "[ERROR] Corrupt conda env; user declined rebuild." 2
exit /b 2
:corrupt_override_exit
echo.
echo   This binary was specified via PVW_CONDA_EXE:
echo     %PVW_CONDA_EXE%
echo.
echo   Automatic self-healing is not available for user-managed conda.
echo   Please fix or replace the binary at the path above, then re-run.
echo.
call :die "[ERROR] Corrupt user-managed conda (PVW_CONDA_EXE); fix manually." 2
exit /b 2
:corrupt_ci_exit
call :die "[ERROR] Corrupt conda binary in CI; cache must be cleared." 2
exit /b 2
:evict_and_rebuild
echo.
echo   [INFO] Removing corrupt Miniconda installation...
rem HP_TEST_SKIP_EVICT: CI branch-coverage flag -- skip actual deletion and re-download so
rem the test does not destroy the CI Miniconda installation. The eviction log line fires to
rem prove the accept branch was reached. Must not be set in production.
if not defined HP_TEST_SKIP_EVICT (
  rmdir /s /q "%MINICONDA_ROOT%" 2>nul
  if exist "%MINICONDA_ROOT%" (
    echo.
    echo   [WARN] Could not fully remove %MINICONDA_ROOT%.
    echo   Some files may be locked. Close any Python/conda windows and try again.
    echo.
    call :die "[ERROR] Could not delete corrupt conda dir; files may be locked." 3
    exit /b 3
  )
  echo   [INFO] Corrupt installation removed. Downloading fresh copy...
)
call :log "[INFO] Self-healing: corrupt conda evicted from %MINICONDA_ROOT%."
set "CONDA_BAT="
set "HP_CONDA_JUST_INSTALLED="
set "HP_ENV_STATE_RESULT="
if not defined HP_TEST_SKIP_EVICT (
  call :download_miniconda_exe
  if exist "%TEMP%\miniconda.exe" (
    call :try_conda_install
  )
  if exist "%TEMP%\miniconda.exe" del "%TEMP%\miniconda.exe" >nul 2>&1
)
call :select_conda_bat
if not defined CONDA_BAT (
  call :die "[ERROR] Fresh Miniconda install failed after self-healing eviction." 4
  exit /b 4
)
set "HP_CONDA_JUST_INSTALLED=1"
goto :after_conda_bat_validation
:conda_bulk_install
rem Run conda bulk install; retry once if output indicates a transient network failure.
rem derived requirement: ~conda_bulk.tmp is tilde-prefixed so it is gitignored and cleaned here.
if exist "~conda_bulk.tmp" del "~conda_bulk.tmp" >nul 2>&1
rem [TEST] HP_TEST_FORCE_CONDA_BULK_FAIL: force a non-transient bulk failure (no retry keyword)
rem so the caller takes the REQ-005.3 per-package fallback. No real conda call is made here.
if "%HP_TEST_FORCE_CONDA_BULK_FAIL%"=="1" (
  call :log "[TEST] HP_TEST_FORCE_CONDA_BULK_FAIL: forcing non-transient bulk failure for REQ-005.3 per-pkg fallback."
  exit /b 1
)
if not "%HP_TEST_FORCE_CONDA_NETWORK_FAIL%"=="1" goto :conda_bulk_real_call
rem [TEST] HP_TEST_FORCE_CONDA_NETWORK_FAIL: simulate a transient CondaHTTPError on first attempt.
echo CondaHTTPError: HTTP 000 CONNECTION FAILED (simulated) > "~conda_bulk.tmp"
set "HP_TEST_FORCE_CONDA_NETWORK_FAIL="
type "~conda_bulk.tmp" >> "%LOG%"
set "HP_CBULK_RC=1"
goto :conda_bulk_have_rc
:conda_bulk_real_call
call "%CONDA_BAT%" install -y -n "%ENVNAME%" --file "~reqs_conda.txt" --override-channels -c conda-forge > "~conda_bulk.tmp" 2>&1
set "HP_CBULK_RC=%ERRORLEVEL%"
type "~conda_bulk.tmp" >> "%LOG%"
:conda_bulk_have_rc
if "%HP_CBULK_RC%"=="0" (
  del "~conda_bulk.tmp" >nul 2>&1
  exit /b 0
)
findstr /i /c:"CondaHTTPError" /c:"Failed to fetch" /c:"timed out" /c:"ConnectionError" "~conda_bulk.tmp" >nul 2>&1
if errorlevel 1 (
  del "~conda_bulk.tmp" >nul 2>&1
  exit /b 1
)
del "~conda_bulk.tmp" >nul 2>&1
echo Conda install failed -- possible network or repository issue. Retrying once...
call :log "[INSTALL] conda bulk: transient failure detected; retrying after 15s."
timeout /t 15 /nobreak >nul 2>&1
echo Retrying package installation...
call "%CONDA_BAT%" install -y -n "%ENVNAME%" --file "~reqs_conda.txt" --override-channels -c conda-forge >> "%LOG%" 2>&1
if not errorlevel 1 exit /b 0
echo *** Package installation could not complete. This may be a temporary network issue.
echo *** See log file for details: ~setup.log
call :log "[WARN] conda bulk: retry after transient failure also failed."
exit /b 1

:try_nuitka_tier_a
rem AV-Safe Build Path (docs/prd-av-safe-build-path.md) requirements 2-4, Tier A only: when the
rem PyInstaller build fails for any reason (build error, or output missing/vanished -- both
rem paths above converge here, per requirement 3's "single trigger category" design), attempt a
rem fallback build via Nuitka directly inside the SAME environment already used for the
rem PyInstaller attempt -- same Python, same installed packages, no reprovisioning. Nuitka does
rem its own internal compiler discovery (MSVC first, then MinGW64 auto-download); per requirement
rem 4's own explicit instruction, this subroutine does NOT probe for a compiler itself -- that
rem kind of fingerprinting is exactly what research Finding 2 already argued against. Tier B
rem (reprovisioned pinned-3.12 environment for the no-compiler-found case) is NOT implemented
rem yet -- this is Tier A only; a Tier A failure currently falls through to the caller's own
rem :warn_build_incomplete site (CLAUDE.md Item 46 Bucket B migrated these callers off :die;
rem see that subroutine's own header comment for why it is not a :die call anymore).
rem Called via `call` (never `goto`) from inside the PyInstaller-build if/else nesting above, so
rem it is safe regardless of block depth -- see the "Nested if/else (no goto)" comment there.
call :log "[INFO] Standard build did not complete; attempting a fallback build (this may take a minute or two)."
if defined HP_TEST_FORCE_NUITKA_FAIL (
  call :log "[TEST] HP_TEST_FORCE_NUITKA_FAIL: simulating fallback build failure."
  exit /b 1
)
if "%HP_ENV_MODE%"=="uv" (
  "%HP_UV_EXE%" pip install --python "%HP_PY%" -q nuitka >> "%LOG%" 2>&1
) else (
  "%HP_PY%" -m pip install -q nuitka >> "%LOG%" 2>&1
)
if errorlevel 1 (
  call :log "[WARN] Fallback build: could not install the fallback build tool; fallback unavailable."
  exit /b 1
)
if exist "dist\%ENVNAME%.exe" del "dist\%ENVNAME%.exe" >nul 2>&1
rem --assume-yes-for-downloads: Nuitka can otherwise prompt interactively to confirm its own
rem dependency downloads (e.g. the MinGW64 compiler) -- fatal for both CI and a real
rem non-interactive double-click user; the Prime Directive tolerates zero prompts here.
"%HP_PY%" -m nuitka --onefile --assume-yes-for-downloads --remove-output --output-dir=dist -o "%ENVNAME%.exe" "%HP_ENTRY%" >> "%LOG%" 2>&1
if errorlevel 1 (
  call :log "[WARN] Fallback build did not complete successfully."
  call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this fallback should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help this fallback succeed."
  exit /b 1
)
if not exist "dist\%ENVNAME%.exe" (
  call :log "[WARN] Fallback build finished but did not produce dist\%ENVNAME%.exe."
  call :log "[WARN] Hint: if you have Visual Studio 2022 (or newer) with the 'Desktop development with C++' workload installed, this fallback should use it automatically -- no extra setup needed. If not, installing the free Visual Studio Build Tools with that workload can help this fallback succeed."
  exit /b 1
)
call :log "[INFO] Fallback build succeeded: dist\%ENVNAME%.exe was produced using the fallback build system."
exit /b 0

:die
set "MSG=%~1"
set "RC=%~2"
if "%RC%"=="" set "RC=1"
echo %date% %time% %MSG%
>> "%LOG%" echo [%date% %time%] %MSG%
rem derived requirement: exit /b here only returns from THIS call frame -- a caller with no
rem halt/goto after "call :die" simply continues (see docs/agent-lessons-learned.md's ":die"
rem entry). If execution later reaches :after_cascade_decision/:after_env_skip with
rem HP_BOOTSTRAP_STATE still "ok", the final status write silently reverts this error back to
rem success. Setting it here, once, covers every call site (already-fixed and future) instead
rem of requiring each one to remember its own companion set.
set "HP_BOOTSTRAP_STATE=error"
call :write_status "error" %RC% %PYCOUNT%
call :release_lock
rem REQ-016: retain terminal window on error so user can read the message.
if not defined HP_CI_LANE (
  pause
)
exit /b %RC%

rem CLAUDE.md Active Backlog Item 46 (Bucket B): a non-pausing sibling of :die for a call site
rem that has failed at ONE task (e.g. every build tool tried) but the run itself is NOT doomed --
rem real, useful work still follows (e.g. the interpreter-fallback verification) that will itself
rem determine the honest final outcome. Deliberately does NOT pause (the run isn't over yet, so
rem stopping here would misleadingly look like the terminal state), does NOT call :release_lock
rem (a concurrent second instance must not be able to start while this run still has real
rem verification work ahead of it), and does NOT call :write_status (the eventual :success/
rem :print_no_exe_briefing dispatch writes the real final status once HP_BOOTSTRAP_STATE is
rem settled). Still sets HP_BOOTSTRAP_STATE=error, same as :die, so the final report is honest.
:warn_build_incomplete
set "MSG=%~1"
call :log "%MSG%"
set "HP_BOOTSTRAP_STATE=error"
exit /b 0

:rotate_log
powershell -NoProfile -ExecutionPolicy Bypass -Command ^
"if (Test-Path '%LOG%') { if ((Get-Item '%LOG%').Length -gt 10485760) { Move-Item -Force '%LOG%' '%LOGPREV%' } }"
exit /b 0
:acquire_lock
rem REQ-024: concurrent-instance (double-click race) protection. mkdir is atomic on NTFS,
rem giving a race-free acquire primitive; CMD has no native mutex. Staleness (age-based, not
rem PID-liveness -- PIDs get reused) is the correctness backstop for the crash/kill/power-loss
rem case, since CMD has no finally/trap to guarantee release on every exit path. See
rem docs/agent-lessons-learned.md "Concurrent-instance lock" for the full design rationale.
set "HP_LOCK_DIR=%HP_SCRIPT_ROOT%~bootstrap.lock"
set "HP_LOCK_OWNED="
if defined HP_TEST_DISABLE_LOCK exit /b 0
mkdir "%HP_LOCK_DIR%" 2>nul
if not errorlevel 1 goto :lock_acquired
call :lock_is_stale
if not errorlevel 1 goto :lock_stale_evict
if errorlevel 2 goto :lock_indeterminate
echo ***
echo *** Another instance of this setup appears to be running in this folder already.
echo *** If you are sure that is NOT the case ^(for example, a previous run crashed^),
echo *** delete the "~bootstrap.lock" folder next to this script and run it again.
echo ***
if exist "%HP_LOCK_DIR%\owner.txt" type "%HP_LOCK_DIR%\owner.txt"
call :log "[WARN] REQ-024: setup already running (lock held, not stale); this instance is exiting."
if not defined HP_CI_LANE pause
exit /b 1
:lock_indeterminate
rem derived requirement: CLAUDE.md Item 49 -- an indeterminate staleness result (neither
rem 'stale' nor 'fresh' came back from the PowerShell probe, e.g. a transient hiccup) must not
rem be treated as "fresh" the way it silently was before this fix, since that produced a false
rem "another instance is running" hard block for a condition that has nothing to do with a
rem concurrent run. Also must not be treated as "stale" (silently evicting a lock that might
rem genuinely still be held by a live instance would be equally wrong) -- so this is a THIRD,
rem distinct outcome: continue without the lock, same graceful shape as the already-existing
rem "could not acquire lock after evicting" path below.
call :log "[WARN] REQ-024: could not determine whether the existing lock is stale (indeterminate result); continuing without it."
exit /b 0
:lock_stale_evict
call :log "[INFO] REQ-024: stale lock evicted (older than the staleness threshold); proceeding."
rd /s /q "%HP_LOCK_DIR%" 2>nul
mkdir "%HP_LOCK_DIR%" 2>nul
if errorlevel 1 (
  call :log "[WARN] REQ-024: could not acquire lock after evicting stale lock; continuing without it."
  exit /b 0
)
:lock_acquired
set "HP_LOCK_OWNED=1"
set "HP_LOCK_PID="
for /f "usebackq delims=" %%P in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "$PID" 2^>nul`) do set "HP_LOCK_PID=%%P"
>"%HP_LOCK_DIR%\owner.txt" echo pid=%HP_LOCK_PID%
>>"%HP_LOCK_DIR%\owner.txt" echo started=%date% %time%
exit /b 0
:lock_is_stale
rem exit/b 0 = stale (caller should evict); exit/b 1 = fresh (still held by a live instance);
rem exit/b 2 = indeterminate (CLAUDE.md Item 49 -- neither recognized value came back from the
rem probe below). derived requirement: HP_TEST_FORCE_LOCK_STALE gives CI a deterministic way to
rem exercise the eviction path without waiting out the real ~2 hour threshold;
rem HP_TEST_FORCE_LOCK_INDETERMINATE similarly exercises the indeterminate path deterministically,
rem without needing to actually simulate a PowerShell hiccup.
if defined HP_TEST_FORCE_LOCK_STALE exit /b 0
if defined HP_TEST_FORCE_LOCK_INDETERMINATE exit /b 2
set "HP_LOCK_STALE_RESULT="
for /f "usebackq delims=" %%R in (`powershell -NoProfile -ExecutionPolicy Bypass -Command "if (Test-Path '%HP_LOCK_DIR%') { try { $d = (Get-Item '%HP_LOCK_DIR%').LastWriteTime; if (((Get-Date)-$d).TotalHours -ge 2) { 'stale' } else { 'fresh' } } catch { 'indeterminate' } } else { 'stale' }" 2^>nul`) do set "HP_LOCK_STALE_RESULT=%%R"
if "%HP_LOCK_STALE_RESULT%"=="stale" exit /b 0
if "%HP_LOCK_STALE_RESULT%"=="fresh" exit /b 1
exit /b 2
:release_lock
if defined HP_LOCK_OWNED (
  rd /s /q "%HP_LOCK_DIR%" 2>nul
  set "HP_LOCK_OWNED="
)
exit /b 0
:run_installer_timeout
rem [Active Backlog item 14] Bounds a "start "" /wait" external-installer launch with a
rem generous, configurable timeout ceiling instead of an unbounded wait -- see
rem tools/run_installer_with_timeout.ps1's own header comment for the full design rationale --
rem UseShellExecute=$true for UAC-elevation-via-manifest parity with plain "start /wait";
rem taskkill /F /T instead of $p.Kill for real process-tree termination, since Windows
rem PowerShell 5.1 / .NET Framework's Process.Kill has no tree-kill overload. This is a
rem safety CEILING against a genuinely hung installer, not a responsiveness check -- killing
rem an installer too early while it is still legitimately writing files/registry keys risks a
rem WORSE outcome (a corrupted half-installed target) than a slow-but-succeeding install, so
rem callers must pick a generous value. See the per-call-site comments for the real-world
rem research behind each timeout value.
rem
rem Params: %1=exe path, %2=argument string (single token; caller's responsibility to already
rem have it as one properly-tokenized string, matching HP_PROBE_ARGS's own "caller provides a
rem ready string" contract), %3=timeout in milliseconds, %4=short label for logging.
rem Returns the installer's real exit code (or 1 on timeout) via errorlevel, exactly like the
rem old bare "start "" /wait" did -- callers' existing "if errorlevel 1 goto ..." need no change.
rem
rem Falls back to the OLD untimed "start "" /wait" behavior if the helper payload cannot be
rem emitted to disk (extremely rare -- disk/permission failure), so a helper-emission problem
rem can never make an installer unreachable outright; it only removes the timeout ceiling for
rem that one run.
set "HP_INSTALLER_LABEL=%~4"
set "HP_INSTALLER_EXE=%~1"
set "HP_INSTALLER_ARGS=%~2"
set "HP_INSTALLER_TIMEOUT_MS=%~3"
set "HP_INSTALLER_RESULT=~installer_result.txt"
set "HP_INSTALLER_RC=1"
set "HP_INSTALLER_TIMEDOUT=0"
if exist "~run_installer_with_timeout.ps1" del "~run_installer_with_timeout.ps1" >nul 2>&1
if exist "%HP_INSTALLER_RESULT%" del "%HP_INSTALLER_RESULT%" >nul 2>&1
call :emit_from_base64 "~run_installer_with_timeout.ps1" HP_INSTALLER_TIMEOUT
if errorlevel 1 goto :installer_timeout_fallback
call :log "[INFO] Launching %HP_INSTALLER_LABEL% installer (timeout ceiling: %HP_INSTALLER_TIMEOUT_MS% ms)."
powershell -NoProfile -ExecutionPolicy Bypass -File "~run_installer_with_timeout.ps1"
if exist "%HP_INSTALLER_RESULT%" (
  for /f "usebackq tokens=1,2 delims=|" %%A in ("%HP_INSTALLER_RESULT%") do (
    set "HP_INSTALLER_RC=%%A"
    set "HP_INSTALLER_TIMEDOUT=%%B"
  )
)
goto :installer_timeout_cleanup
:installer_timeout_fallback
call :log "[WARN] Installer timeout helper could not be emitted; running %HP_INSTALLER_LABEL% without a timeout ceiling."
start "" /wait "%HP_INSTALLER_EXE%" %HP_INSTALLER_ARGS%
set "HP_INSTALLER_RC=%ERRORLEVEL%"
goto :installer_timeout_done
:installer_timeout_cleanup
if "%HP_INSTALLER_TIMEDOUT%"=="1" call :log "[WARN] %HP_INSTALLER_LABEL% installer exceeded its %HP_INSTALLER_TIMEOUT_MS% ms timeout ceiling; terminated."
if exist "~run_installer_with_timeout.ps1" del "~run_installer_with_timeout.ps1" >nul 2>&1
if exist "%HP_INSTALLER_RESULT%" del "%HP_INSTALLER_RESULT%" >nul 2>&1
:installer_timeout_done
set "HP_INSTALLER_EXE="
set "HP_INSTALLER_ARGS="
set "HP_INSTALLER_TIMEOUT_MS="
set "HP_INSTALLER_RESULT="
set "HP_INSTALLER_LABEL="
rem derived requirement: HP_INSTALLER_TIMEDOUT is deliberately NOT cleared here -- it is the
rem only way a caller can tell a timeout (HP_INSTALLER_RC hardcoded to 1, not a real installer
rem exit code) apart from a genuine installer failure that happens to also exit 1. Each call
rem freshly re-sets it at entry (either "0" or from the result file), so leaving it live across
rem return is safe: no caller can ever read a stale value from an earlier, different call.
exit /b %HP_INSTALLER_RC%
:try_conda_install
rem derived requirement: AllUsers install can fail when UAC rejects elevation even for admin accounts.
rem JustMe is the non-admin fallback that installs under the user profile instead.
rem Both attempts reuse the already-downloaded installer at %TEMP%\miniconda.exe (no re-download).
rem derived requirement: [Active Backlog item 16, renumbered from 11 -- see docs/agent-closed-backlog.md]
rem track whether AllUsers was actually launched vs.
rem only skipped, so :tci_justme's own log line can tell the two apart instead of unconditionally
rem claiming AllUsers "failed" even when it was never attempted. Reset defensively at entry in case
rem a future caller invokes this subroutine more than once in the same process.
set "HP_CONDA_ALLUSERS_ATTEMPTED="
set "HP_CONDA_ALLUSERS_TIMEDOUT="
rem derived requirement: non-admin machines produce a UAC prompt when AllUsers install is attempted;
rem skip directly to JustMe when the process is not elevated.
rem HP_TEST_NOT_ELEVATED=1 simulates a non-admin environment for CI coverage of this branch.
if "%HP_TEST_NOT_ELEVATED%"=="1" (
  call :log "[INFO] Not elevated; skipping AllUsers Miniconda install."
  goto :tci_justme
)
fsutil dirty query %systemdrive% >nul 2>&1
if errorlevel 1 (
  call :log "[INFO] Not elevated; skipping AllUsers Miniconda install."
  goto :tci_justme
)
rem derived requirement: [Active Backlog item 14] bound the installer launch with a generous
rem timeout ceiling instead of an unbounded wait. Real-world reports (Travis CI Community forum:
rem "Installing Anaconda/Miniconda times out after 40 minutes"; multiple conda/conda and
rem ContinuumIO/anaconda-issues GitHub issues reporting the silent installer hanging indefinitely
rem at extraction or the post-install script) confirm this is not a theoretical risk. 60 minutes
rem is a generous ceiling above the documented ~40 min real-world duration.
set "HP_CONDA_ALLUSERS_ATTEMPTED=1"
set "HP_CONDA_ALLUSERS_RC="
call :run_installer_timeout "%TEMP%\miniconda.exe" "/InstallationType=AllUsers /AddToPath=0 /RegisterPython=0 /S /D=%MINICONDA_ROOT%" 3600000 "Miniconda AllUsers"
set "HP_CONDA_ALLUSERS_RC=%ERRORLEVEL%"
set "HP_CONDA_ALLUSERS_TIMEDOUT=%HP_INSTALLER_TIMEDOUT%"
if not "%HP_CONDA_ALLUSERS_RC%"=="0" goto :tci_justme
set "HP_CONDA_INSTALL_MODE=AllUsers"
call :log "[INFO] Miniconda installed successfully."
goto :eof
:tci_justme
rem derived requirement: a genuine timeout (HP_CONDA_ALLUSERS_TIMEDOUT=1, see
rem :run_installer_timeout's own header comment) hardcodes HP_CONDA_ALLUSERS_RC to 1 -- that is
rem a sentinel, not the installer's real exit code, so it must not be presented as one; report
rem reason=timeout instead of a fabricated exitCode.
if defined HP_CONDA_ALLUSERS_ATTEMPTED (
  if "%HP_CONDA_ALLUSERS_TIMEDOUT%"=="1" (
    call :log "[WARN] Miniconda AllUsers install failed (reason=timeout); retrying with JustMe."
  ) else (
    call :log "[WARN] Miniconda AllUsers install failed (exitCode=%HP_CONDA_ALLUSERS_RC%, reason=installer_failed); retrying with JustMe."
  )
) else (
  call :log "[INFO] Miniconda AllUsers install skipped (not elevated); trying JustMe install instead."
)
if exist "%MINICONDA_ROOT%" rd /s /q "%MINICONDA_ROOT%" >nul 2>&1
rem derived requirement: [Active Backlog item 10] HP_TEST_FORCE_JUSTME_FAIL=1 deterministically
rem forces the JustMe install to fail WITHOUT launching the real installer, so CI can exercise
rem :tci_both_failed (both AllUsers and JustMe failing) without depending on a genuinely broken
rem installer/ACL environment. HP_TEST_NOT_ELEVATED=1 alone already reaches this label by
rem skipping straight past the AllUsers attempt; this flag makes the JustMe attempt fail too.
if "%HP_TEST_FORCE_JUSTME_FAIL%"=="1" (
  call :log "[INFO] HP_TEST_FORCE_JUSTME_FAIL=1; simulating JustMe install failure without launching the real installer."
  goto :tci_both_failed
)
call :run_installer_timeout "%TEMP%\miniconda.exe" "/InstallationType=JustMe /AddToPath=0 /RegisterPython=0 /S /D=%MINICONDA_ROOT%" 3600000 "Miniconda JustMe"
if errorlevel 1 goto :tci_both_failed
set "HP_CONDA_INSTALL_MODE=JustMe"
call :log "[INFO] Miniconda installed (JustMe fallback)."
goto :eof
:tci_both_failed
rem derived requirement: [Active Backlog item 16] the terminal message must not imply AllUsers was
rem genuinely attempted-then-failed when it was only ever skipped (not elevated) -- mirrors the
rem same HP_CONDA_ALLUSERS_ATTEMPTED distinction :tci_justme already makes above.
if defined HP_CONDA_ALLUSERS_ATTEMPTED (
  call :die "[ERROR] Miniconda install failed (both AllUsers and JustMe)."
) else (
  call :die "[ERROR] Miniconda install failed (AllUsers skipped -- not elevated; JustMe also failed)."
)
goto :eof

:conda_base_update
if /i not "%HP_ENV_MODE%"=="conda" goto :eof
if "%HP_TEST_CONDA_UPDATE%"=="1" (
  if exist "%TEMP%\~conda.update.done" (
    call :log "[INFO] Conda base update: skipped (already ran this session)."
    goto :eof
  )
  goto :cbu_run
)
if defined HP_CONDA_JUST_INSTALLED goto :cbu_firstinstall
set "HP_CONDA_UPDATE_RESULT=update"
rem derived requirement: raw .NET File/DateTime APIs, not Get-Content/Get-Date/Test-Path --
rem see docs/agent-lessons-learned.md "Prefer raw .NET types over Utility-module cmdlets" for
rem the confirmed Windows PowerShell 5.1 module-autoload gap this avoids (Get-FileHash failed
rem to autoload in the identical for-/f-backtick-subshell topology; Get-Date/Get-Content share
rem its Microsoft.PowerShell.Utility module, so both are equally exposed, not just the one that
rem was actually caught failing).
for /f "delims=" %%R in ('powershell -NoProfile -ExecutionPolicy Bypass -Command "if ([System.IO.File]::Exists('%MINICONDA_ROOT%\~conda.lastupdate')) { try { $d = [datetime]([System.IO.File]::ReadAllText('%MINICONDA_ROOT%\~conda.lastupdate', [System.Text.Encoding]::ASCII)); if (([datetime]::Now - $d).TotalDays -ge 30) { 'update' } else { 'skip' } } catch { 'update' } } else { 'update' }"') do set "HP_CONDA_UPDATE_RESULT=%%R"
if "%HP_CONDA_UPDATE_RESULT%"=="update" goto :cbu_run
call :log "[INFO] Conda base update: skipped (last update < 30 days ago)."
goto :eof

:cbu_firstinstall
call :log "[INFO] Conda base update: skipped (first install)."
powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.IO.File]::WriteAllText('%MINICONDA_ROOT%\~conda.lastupdate', [datetime]::Now.ToString('yyyy-MM-ddTHH:mm:ss'), [System.Text.Encoding]::ASCII)" >nul 2>&1
goto :eof

:cbu_run
call :log "[INFO] Conda base update: running (>=30 days since last update or no record)."
call "%CONDA_BAT%" update -n base --all --override-channels -c conda-forge -y >> "%LOG%" 2>&1
if not errorlevel 1 (
  powershell -NoProfile -ExecutionPolicy Bypass -Command "[System.IO.File]::WriteAllText('%MINICONDA_ROOT%\~conda.lastupdate', [datetime]::Now.ToString('yyyy-MM-ddTHH:mm:ss'), [System.Text.Encoding]::ASCII)" >nul 2>&1
  call :log "[INFO] Conda base update complete."
) else (
  call :log "[WARN] Conda base update failed; continuing."
)
type nul > "%TEMP%\~conda.update.done" 2>nul
goto :eof

:merge_git_config
rem REQ-015: idempotently append standard .gitignore and .gitattributes entries.
rem Uses findstr errorlevel: 0=found (skip), 1=not found, 2=file missing; both 1 and 2 trigger append.
set "HP_GI_SIG=# Automated Python Bootstrapper Standard Ignores"
set "HP_GA_SIG=# Automated Python Bootstrapper Attributes"
findstr /C:"%HP_GI_SIG%" ".gitignore" >nul 2>&1
if not errorlevel 1 goto :mgc_gi_done
call :log "[INFO] REQ-015: Appending standard ignores to .gitignore."
>> ".gitignore" echo.
>> ".gitignore" echo %HP_GI_SIG%
>> ".gitignore" echo .*_env/
>> ".gitignore" echo .venv/
>> ".gitignore" echo .uv/
>> ".gitignore" echo .cache/
>> ".gitignore" echo .conda/
>> ".gitignore" echo dist/
>> ".gitignore" echo build/
>> ".gitignore" echo *~
>> ".gitignore" echo ~*
:mgc_gi_done
findstr /C:"%HP_GA_SIG%" ".gitattributes" >nul 2>&1
if not errorlevel 1 goto :mgc_ga_done
call :log "[INFO] REQ-015: Appending standard attributes to .gitattributes."
rem derived requirement: "-text" (not "eol=crlf") for *.bat/*.cmd -- eol=crlf only affects
rem checkout, never the blob a raw/GitHub-served download returns, so a user's own .bat files
rem would still be corrupted for anyone downloading them raw from THEIR repo. This repo's own
rem docs/agent-lessons-learned.md ".bat files: -text, not eol=crlf" entry is the full writeup
rem of why; propagate the fix this bootstrapper itself needed, not the pattern proven insufficient.
>> ".gitattributes" echo.
>> ".gitattributes" echo %HP_GA_SIG%
>> ".gitattributes" echo *.bat -text
>> ".gitattributes" echo *.cmd -text
>> ".gitattributes" echo *.exe binary
:mgc_ga_done
rem CLAUDE.md Item 60: a user who already ran an OLDER copy of this bootstrapper has the
rem HP_GA_SIG signature present, so the findstr check above skips the whole append block on a
rem newer run -- their .gitattributes stays on the disproven *.bat/*.cmd eol=crlf rule
rem indefinitely with no path to the fix. This label is reached both when the signature was
rem already found AND right after a fresh append, so .gitattributes always exists here.
rem tools/migrate_gitattributes.ps1 replaces ONLY lines that exactly match one of the two
rem known-stale strings, in place -- every other line (including any user hand-edits
rem elsewhere in the file) passes through byte-identical.
call :emit_from_base64 "~migrate_gitattributes.ps1" HP_MIGRATE_GITATTRIBUTES
powershell -NoProfile -ExecutionPolicy Bypass -File "~migrate_gitattributes.ps1" -Path ".gitattributes" > "~ga_migrate_result.txt" 2>&1
if exist "~migrate_gitattributes.ps1" del "~migrate_gitattributes.ps1" >nul 2>&1
set "HP_GA_MIGRATE_RESULT="
rem derived requirement: read ONLY the first line of the result file into HP_GA_MIGRATE_RESULT
rem -- the "if not defined" guard skips every later iteration once the first non-blank line has
rem been captured. A stray extra line (e.g. a multi-line terminating-error dump the script's own
rem try/catch is meant to prevent, but defense in depth costs nothing here) must never silently
rem overwrite the real marker with unrelated trailing text -- the prior unguarded loop kept only
rem the LAST line instead, which is the wrong end of the file for this purpose.
for /f "usebackq delims=" %%X in ("~ga_migrate_result.txt") do if not defined HP_GA_MIGRATE_RESULT set "HP_GA_MIGRATE_RESULT=%%X"
if "%HP_GA_MIGRATE_RESULT%"=="MIGRATED" goto :mgc_ga_migrated
if "%HP_GA_MIGRATE_RESULT%"=="NOOP:no-stale-lines" goto :mgc_ga_migrate_end
if "%HP_GA_MIGRATE_RESULT%"=="NOOP:missing" goto :mgc_ga_migrate_end
rem derived requirement: this is a best-effort, non-gating hygiene check (mirrors this repo's
rem own established "never gates the lane" convention for similar helpers, e.g. autopep_merge
rem -- see docs/agent-interconnect.md) -- an unexpected result (script threw, or produced text
rem outside its own 3-value contract) is logged for visibility but never treated as fatal.
call :log "[WARN] REQ-015: .gitattributes migration check produced an unexpected result (non-fatal, continuing)."
rem derived requirement: dump the script's own raw captured output, stdout and stderr both, via
rem a plain byte copy, never through %VAR% substitution -- an unanticipated failure message
rem could legally contain shell metacharacters such as less-than, greater-than, ampersand,
rem pipe, percent, or caret, the exact hazard :log's own UNQUOTED echo is not safe against --
rem see docs/agent-lessons-learned.md's ":log echoes UNQUOTED" entry. "type" reads file bytes
rem only; it never re-parses them as command text.
if exist "~ga_migrate_result.txt" type "~ga_migrate_result.txt" 2>nul
if exist "~ga_migrate_result.txt" type "~ga_migrate_result.txt" >> "%LOG%" 2>nul
goto :mgc_ga_migrate_end
:mgc_ga_migrated
call :log "[INFO] REQ-015: Migrated stale *.bat/*.cmd eol=crlf rule to -text in .gitattributes."
:mgc_ga_migrate_end
if exist "~ga_migrate_result.txt" del "~ga_migrate_result.txt" >nul 2>&1
set "HP_GA_MIGRATE_RESULT="
set "HP_GI_SIG="
set "HP_GA_SIG="
exit /b 0

:print_postflight_briefing
rem REQ-016: print a scannable summary panel after a successful full EXE build.
echo.
echo ============================================================
if defined HP_EXE_VERIFY_FAILED goto :pfb_caveat
echo  SETUP COMPLETE
echo ============================================================
echo  Your standalone application is ready:
echo    dist\%ENVNAME%.exe
if defined HP_EXE_SKIPPED echo    Note: EXE verification was skipped by request (HP_SKIP_EXE_SMOKERUN).
goto :pfb_runapp
:pfb_caveat
echo  SETUP COMPLETE -- WITH A CAVEAT
echo ============================================================
echo  We packaged your app, but couldn't fully verify it runs as a
echo  standalone program. Your Python environment was set up and the
echo  packaging step completed without a fatal error.
if defined HP_EXE_TIMEDOUT_SILENT call :pfb_gui_hint
if defined HP_DLL_HINT_STATE call :pfb_dll_hint
:pfb_runapp
echo.
echo  RUNNING YOUR APP
echo    Double-click dist\%ENVNAME%.exe to run it.
rem derived requirement: defense in depth alongside the HP_CASCADE_SAVED_PY restore above --
rem never print an empty "" "%HP_ENTRY%" interpreter command. HP_PY should always be defined
rem here (the EXE this panel describes could not have been built without a working interpreter,
rem and :after_cascade_decision now restores HP_PY if a declined/exhausted cascade tier cleared
rem it), but the .exe itself remains correct and runnable either way, so silently omitting this
rem one line is strictly safer than a guaranteed-broken command.
if not defined HP_PY goto :pfb_runapp_noninterp
echo    You can also run it directly via the interpreter at any time:
echo      "%HP_PY%" "%HP_ENTRY%"
:pfb_runapp_noninterp
echo.
echo    STARTUP MAY BE SLOW: a one-file .exe unpacks itself each time it
echo    starts, so allow 10-15 seconds (longer for big libraries like
echo    numpy/scipy/matplotlib, or when extra packages were bundled to fix
echo    missing imports) before assuming it has hung.
echo.
echo    If the window flashes and closes instantly: that's normal if
echo    your program finished quickly or hit an error before printing
echo    anything. To see what happened, open Command Prompt, cd to
echo    this folder, and run:
echo      dist\%ENVNAME%.exe
echo    This keeps the window open so you can read any messages.
echo.
echo    A progress indicator that updates in place may appear all at
echo    once instead of live when run as the .exe -- that is a stdout
echo    buffering difference between the .exe and the script, not an error.
echo.
echo    Does your program need launch arguments (e.g. --input file.csv)? Run
echo    this bootstrapper again with them added after the entry file, e.g.
echo      run_setup.bat "%HP_ENTRY%" --input file.csv
echo    and they will be forwarded to your program during THIS setup run
echo    (up to 8 extra arguments). This does not change how a plain
echo    double-click of dist\%ENVNAME%.exe launches it afterward -- for that,
echo    make a Windows shortcut to the .exe and add the arguments to its
echo    Target field, or launch it yourself from a Command Prompt.
echo.
echo  KEEP these files with your project:
echo    requirements.txt  -- packages your app depends on
echo    runtime.txt       -- Python version pin
echo.
echo  SAFE TO DELETE to reclaim disk space:
echo    .*_env\ folders   -- environment directories
echo    ~* files          -- tilde-prefix work files (e.g. ~setup.log)
echo    build\            -- PyInstaller build cache
echo ============================================================
echo.
if defined HP_EXE_VERIFY_FAILED goto :pfb_log_caveat
call :log "[INFO] REQ-016: Post-flight briefing printed."
exit /b 0
:pfb_log_caveat
call :log "[WARN] REQ-016: Post-flight briefing printed; EXE unverified, advised direct run."
exit /b 0

:pfb_dll_hint
rem docs/open-questions.md item 1 (answered yes, implemented): a real, already-computed fact
rem (:dll_bundle_recover's own outcome -- CLAUDE.md Item 24/25/28/29) surfacing in the caveat
rem panel above, instead of staying purely generic. Three wording buckets, not one -- a review
rem pass on PR #414 (see docs/agent-interconnect.md's DLL-bundling section) found detection and
rem repair are NOT the same event, so the wording must not claim more than each state actually
rem means: "skipped" states never attempted a fix; "failed"/"exhausted" states attempted but did
rem not fully resolve it; "repaired" means the DLL itself was fixed (this caveat firing anyway
rem means something ELSE is still wrong -- HP_DLL_REPAIRED can trigger a bounded follow-up
rem :hidden_import_recover pass that still leaves HP_EXE_EXIT non-zero for an unrelated reason,
rem so this is rare but real, not dead code). goto-based dispatch (not a parenthesized block) so
rem each %VAR% reads its runtime value -- see docs/agent-lessons-learned.md's "Provider-cascade
rem dispatch is goto-based on purpose". HP_DLL_DETECTED_SAFE was already sanitized (ampersand,
rem pipe, angle brackets, percent, and caret all stripped) for :log's own unquoted echo, so it
rem is equally safe on this panel's plain echo.
if "%HP_DLL_HINT_STATE%"=="repaired" goto :pfb_dll_hint_repaired
if "%HP_DLL_HINT_STATE%"=="failed_rebuild" goto :pfb_dll_hint_failed
if "%HP_DLL_HINT_STATE%"=="failed_missing_exe" goto :pfb_dll_hint_failed
if "%HP_DLL_HINT_STATE%"=="exhausted" goto :pfb_dll_hint_failed
goto :pfb_dll_hint_skipped
:pfb_dll_hint_skipped
rem Covers skipped_nuitka / skipped_non_conda / unlocatable: detection happened, but no repair
rem was ever attempted for it -- must NOT claim "we tried to fix it automatically."
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app, but no automatic repair for it ran during this build.
echo  This may or may not be related to the caveat above.
goto :pfb_dll_hint_done
:pfb_dll_hint_failed
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app, and an automatic repair was attempted but did not fully
echo  resolve it. This may be related to the caveat above.
goto :pfb_dll_hint_done
:pfb_dll_hint_repaired
echo.
echo  NOTE: a missing native library (%HP_DLL_DETECTED_SAFE%) was detected while
echo  packaging your app and was automatically repaired; it is likely not the
echo  cause of the caveat above.
:pfb_dll_hint_done
exit /b 0

:pfb_gui_hint
rem CLAUDE.md Active Backlog Item 41: the ~30s verification kill only fires when ZERO
rem stdout/stderr bytes were observed before the deadline (~exe_smokerun.ps1's own $sawOutput
rem gate, see docs/agent-interconnect.md "Activity-aware EXE-smoke kill") -- exactly the shape
rem of a correctly-behaving GUI app (tkinter, PyQt) with a mainloop and no console output, not
rem just a genuinely hung program. Distinguish the two in the panel text itself, not just the
rem pre-launch warning (:warn_user_code_launch already discloses the kill beforehand, but that
rem disclosure and this panel are both worded for a console-program mental model otherwise).
echo.
echo  NOTE: this can happen for a GUI app, e.g. tkinter or PyQt, that opens its
echo  own window and prints nothing to the console -- that produces the exact
echo  same silence as a genuinely hung program, so we cannot tell them apart
echo  automatically. If a window appeared and worked normally, this caveat
echo  does not necessarily mean anything is wrong.
exit /b 0

:print_no_exe_briefing
rem docs/open-questions.md item 1: when PyInstaller AND the Nuitka fallback both fail outright
rem (no dist\%ENVNAME%.exe at all), the run still succeeds via the interpreter fallback and the
rem final [STATUS] line reads identically to a real EXE success, with the only prior signal
rem being one [ERROR] line several seconds earlier. This panel closes that gap -- purely
rem additive, does not touch success/failure semantics (~bootstrap.status.json already
rem correctly records state=error via :die's own HP_BOOTSTRAP_STATE=error).
rem [REQ-027] P2 honest messaging: the header text below used to unconditionally claim "your
rem code ran successfully" regardless of whether the interpreter run (:verify_no_exe_interpreter,
rem which always runs immediately before this panel on the no-EXE path) actually exited 0 -- a
rem real, pre-existing dishonest claim, confirmed by tracing that HP_NOEXE_VERIFY_FAILED is the
rem ONLY signal this panel previously ignored. Branches on it now, mirroring
rem :print_postflight_briefing's own :pfb_caveat dispatch shape.
echo.
echo ============================================================
if defined HP_NOEXE_VERIFY_FAILED goto :noexe_caveat
echo  YOUR CODE RAN -- BUT NO STANDALONE .EXE WAS PRODUCED
echo ============================================================
echo  We could not package your app into a double-clickable .exe
echo  (see the ERROR message above for why), but your code ran
echo  successfully just now using the prepared Python environment.
echo  Your environment and dependencies ARE installed correctly.
goto :noexe_runapp
:noexe_caveat
echo  NO STANDALONE .EXE -- AND WE CAN'T CONFIRM YOUR CODE RAN CLEANLY
echo ============================================================
echo  We could not package your app into a double-clickable .exe
echo  (see the ERROR message above for why). We also just ran it
echo  directly via the prepared Python environment, and it exited
echo  with an error (see the [STATUS] line above) -- so we can't
echo  tell whether that's a bug in the Python code we tried to run
echo  or something this bootstrapper missed. Your environment and
echo  dependencies ARE still installed correctly; run it yourself
echo  below to see the full output.
:noexe_runapp
echo.
echo  RUNNING YOUR APP (without an .exe) -- the most direct option
echo    "%HP_PY%" "%HP_ENTRY%"
echo    Need launch arguments? Add them directly after that command, e.g.
echo      "%HP_PY%" "%HP_ENTRY%" --input file.csv
echo.
if defined HP_NOEXE_VERIFY_FAILED (
  echo  Want to try different arguments through the bootstrapper itself
  echo  instead? Your already-installed environment is reused either
  echo  way; it will just attempt the .exe build again too:
  echo    run_setup.bat "%HP_ENTRY%" arg1 arg2
  echo.
)
echo  KEEP these files with your project:
echo    requirements.txt  -- packages your app depends on
echo    runtime.txt       -- Python version pin
echo.
echo  SAFE TO DELETE to reclaim disk space:
echo    .*_env\ folders   -- environment directories
echo    ~* files          -- tilde-prefix work files (e.g. ~setup.log)
echo ============================================================
echo.
if defined HP_NOEXE_VERIFY_FAILED (
  call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, interpreter run also unverified."
) else (
  call :log "[WARN] REQ-016: Post-flight briefing printed; no EXE produced, advised direct interpreter run."
)
exit /b 0

:print_fastpath_ambiguous_note
rem [REQ-027] P2 honest messaging (docs/plan-cli-interactive-verification.md): the cached-EXE
rem fast path is deliberately zero-friction for PROMPTS (never a consent gate, per
rem docs/agent-interconnect.md's "Fast path = ZERO friction" design requirement) -- this is a
rem plain informational print, not a question, so it does not violate that requirement. Fires
rem only when the fail-fast probe classified the reused EXE as alive/healthy (so it was kept,
rem not discarded+rebuilt) and it later exited non-zero -- see :try_fast_exe. Whether that
rem non-zero exit means a real bug in the user's own code, an unresolved dependency, or
rem something else entirely is NOT something this bootstrapper can determine (Open Question 3,
rem same plan doc) -- this note is deliberately honest about that limit, not a diagnosis.
echo.
echo ============================================================
echo  SETUP COMPLETE -- BUT WE CAN'T CONFIRM YOUR LAST RUN WORKED
echo ============================================================
echo  Your existing standalone application was reused (dist\%ENVNAME%.exe),
echo  and it exited with an error just now (see the [STATUS] line
echo  above) -- so we can't tell whether that's a bug in the Python
echo  code we tried to run, or something else. Your environment and
echo  dependencies ARE still installed correctly.
echo.
echo  RUNNING YOUR APP
echo    Double-click dist\%ENVNAME%.exe to run it, or run it from a
echo    Command Prompt to see the full output.
echo.
echo  WANT TO TRY AGAIN? You do not have to start over from scratch --
echo    just run this bootstrapper again the same way you did before;
echo    your already-installed environment and built .exe are reused.
echo.
echo  WANT A FRESH BUILD instead (re-checks all dependencies from scratch)?
echo    Delete dist\%ENVNAME%.exe and run this bootstrapper again.
echo ============================================================
echo.
call :log "[WARN] REQ-016: Post-flight note printed; cached EXE kept despite a non-zero exit after the fail-fast probe."
exit /b 0

:check_net_after_dl_fail
rem REQ-013: called after a primary download fails. Pings 8.8.8.8 to distinguish
rem no-internet (Scenario A) from specific-URL-failed (Scenario B).
rem HP_TEST_OFFLINE=1 simulates ping failure for CI branch coverage.
rem derived requirement: both the ping and curl reachability checks retry once (2 total
rem attempts each) before concluding "no internet" -- a single dropped ICMP echo or a
rem momentarily-contended curl connect on a busy shared CI runner is enough to misclassify a
rem genuinely-online host as offline (root-caused from a real self.ux.connectivity.online CI
rem flake). Mirrors the REQ-022 detect-transient-then-retry-once idiom used elsewhere in this
rem file (:try_conda_create/:conda_bulk_install). The counter-based loop below is safe because
rem these lines are top-level (not nested in a parenthesized block); the "Y" retry branch
rem further down uses literal duplication instead of a counter, since it IS nested in a block
rem and a counter set+read inside the same block would hit CMD's parse-time %VAR% expansion
rem trap (see docs/agent-lessons-learned.md).
if "%HP_TEST_OFFLINE%"=="1" (
  call :log "[TEST] HP_TEST_OFFLINE: simulating ping failure for REQ-013."
  goto :cndf_ping_failed
)
set "HP_CONN_PING_ATTEMPT=0"
:cndf_ping_retry
set /a HP_CONN_PING_ATTEMPT+=1
ping -n 1 8.8.8.8 >nul 2>&1
if not errorlevel 1 (
  call :log "[INFO] REQ-013: Connectivity check: internet reachable. Cascading to fallback."
  exit /b 0
)
if %HP_CONN_PING_ATTEMPT% LSS 2 goto :cndf_ping_retry
rem ICMP may be blocked on corporate networks; try HTTPS as secondary reachability check.
set "HP_CONN_CURL_ATTEMPT=0"
:cndf_curl_retry
set /a HP_CONN_CURL_ATTEMPT+=1
curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
if not errorlevel 1 (
  call :log "[INFO] REQ-013: Connectivity check: internet reachable via HTTPS (ICMP blocked). Cascading to fallback."
  exit /b 0
)
if %HP_CONN_CURL_ATTEMPT% LSS 2 goto :cndf_curl_retry
:cndf_ping_failed
call :log "[WARN] REQ-013: Connectivity check: no internet detected (ICMP and HTTPS check failed)."
:cndf_prompt_loop
rem CLAUDE.md Active Backlog Item 50: this prompt previously had no CI-safe auto-decline at
rem all, unlike every sibling consent gate -- against a genuinely open, interactive console
rem with nobody present to answer, it would block on set /p indefinitely. Fixed with
rem HP_TEST_CNDF_ANSWER (deterministic test override) and NOINPUT/HP_NONINTERACTIVE
rem auto-decline (the same authoritative "no human will answer" signals :run_postexec_
rem checkpoint already treats this way -- see docs/agent-interconnect.md).
rem
rem Deliberately does NOT auto-decline on bare HP_CI_LANE, unlike most sibling gates: two
rem existing regression tests (self.ux.connectivity.offline.n/.prompt.shown/.retry in
rem selfapps_ux_hardening.ps1) already run this exact gate under HP_CI_LANE=test while
rem piping real Y/N answers via stdin, specifically to exercise the interactive Y-retry
rem loop -- a static HP_CI_LANE auto-decline would silently bypass set /p and break that
rem coverage. A genuinely detached CI job (closed/EOF stdin) already resolves safely via the
rem pre-existing empty-input default below regardless of HP_CI_LANE; NOINPUT/HP_NONINTERACTIVE
rem are the correct signal for "deliberately unattended," matching this repo's own convention
rem that those two flags -- not HP_CI_LANE -- represent an explicit non-interactivity
rem declaration (see docs/agent-lessons-learned.md's "Env-var flags are scaffolding" entry).
echo WARNING: No internet connection detected. Remote providers may fail. Retry? (Fix connection then press Y) or proceed offline (N):
set "HP_CONN_CHOICE="
if defined HP_TEST_CNDF_ANSWER (
  set "HP_CONN_CHOICE=%HP_TEST_CNDF_ANSWER%"
) else if defined NOINPUT (
  set "HP_CONN_CHOICE=n"
) else if defined HP_NONINTERACTIVE (
  set "HP_CONN_CHOICE=n"
) else (
  set /p "HP_CONN_CHOICE=Your choice [y/n]: "
)
if "%HP_CONN_CHOICE:~0,1%"=="" (
  call :log "[INFO] REQ-013: Connectivity prompt: empty input; defaulting offline."
  set "HP_OFFLINE_MODE=1"
  exit /b 1
)
if /I "%HP_CONN_CHOICE:~0,1%"=="y" (
  if "%HP_TEST_OFFLINE%"=="1" (
    call :log "[TEST] HP_TEST_OFFLINE: Y selected; still simulating offline."
    goto :cndf_ping_failed
  )
  rem derived requirement: 2 literal attempts each, not a counter var -- this whole branch is
  rem nested inside the "y" parenthesized block, and a counter set+read inside the same block
  rem would be frozen at the block's pre-execution value by CMD's parse-time %VAR% expansion,
  rem see docs/agent-lessons-learned.md; literal duplication has no such variable to freeze.
  ping -n 1 8.8.8.8 >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry."
    exit /b 0
  )
  ping -n 1 8.8.8.8 >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry."
    exit /b 0
  )
  curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)."
    exit /b 0
  )
  curl -s --connect-timeout 5 --max-time 8 -o nul "https://conda.anaconda.org" >nul 2>&1
  if not errorlevel 1 (
    call :log "[INFO] REQ-013: Connectivity restored after retry (HTTPS, ICMP blocked)."
    exit /b 0
  )
  call :log "[INFO] REQ-013: Still offline after Y; re-prompting."
  goto :cndf_prompt_loop
)
call :log "[INFO] REQ-013: Connectivity prompt: user chose offline (N)."
set "HP_OFFLINE_MODE=1"
exit /b 1

:system_python_consent_gate
rem REQ-014: halt and require explicit consent before using global system Python.
rem CI-safe (mirrors :cascade_consent_gate): HP_TEST_SYSCON_ANSWER (Y/N) overrides; otherwise
rem HP_CI_LANE auto-declines with no prompt (no set /p hang in CI); interactive users get a
rem y/n prompt. The prompt string is echoed unconditionally so it appears even on the
rem auto-decline path. exit 0 = accepted, exit 1 = declined.
echo.
echo *** WARNING: System Python Execution ***
echo *** Using global system Python may pollute shared packages. ***
echo.
echo Proceed with System Python? (Global pollution risk) [y/n]: y to accept, n to decline.
set "HP_SYSCON_CHOICE="
if defined HP_TEST_SYSCON_ANSWER (
  set "HP_SYSCON_CHOICE=%HP_TEST_SYSCON_ANSWER%"
) else if defined HP_CI_LANE (
  set "HP_SYSCON_CHOICE=n"
) else (
  set /p "HP_SYSCON_CHOICE=Your choice [y/n]: "
)
if "%HP_SYSCON_CHOICE:~0,1%"=="" (
  call :log "[INFO] REQ-014: System Python consent: empty input; declining."
  exit /b 1
)
if /I "%HP_SYSCON_CHOICE:~0,1%"=="y" (
  call :log "[INFO] REQ-014: System Python consent: user accepted."
  exit /b 0
)
call :log "[INFO] REQ-014: System Python consent: user declined."
exit /b 1

:hp_test_conda_fail
call :log "[TEST] HP_TEST_FORCE_CONDA_FAIL: simulating conda env creation failure."
set "HP_ENV_READY="
call :handle_conda_failure "[TEST] conda env create forced to fail."
if defined HP_ENV_READY goto :after_env_mode_selection
call :die "[ERROR] conda env create failed."
goto :after_env_mode_selection
