Skip to content

feat: Windows/macOS packaging groundwork - #40

Merged
Dimoshka merged 26 commits into
mainfrom
chore/packaging-windows-macos
Nov 11, 2026
Merged

Dimoshka merged 26 commits into
mainfrom
chore/packaging-windows-macos

Conversation

@Dimoshka

@Dimoshka Dimoshka commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Steps 1–9 of the Windows/macOS packaging track (#39). This PR adds the path seams, the macOS sandbox decision, a packaging tool that stages a bundle, writes the platform archive and verifies it, CI jobs for both targets, and the documentation and checkpoint refresh. Issue #39's final ServerBinaryLocatorImpl pathContext item is not part of this PR.

Step 1 — path-context seam in the library locator

LlamaLibraryLocator takes an optional package:path Context. Every join/normalize/isAbsolute/dirname/separator call now goes through _path, which defaults to p.context. Production is unchanged; tests pass a windows or posix context to exercise Windows and macOS path shapes on Linux.

  • Windows candidates: <exeDir>\lib\llama.dll, <exeDir>\llama.dll, <exeDir>\..\Frameworks\llama.dll, the config lib\llama.dll, bare llama.dll last.
  • macOS candidates: the per-exe lib/ and Frameworks/ dylibs, then the bundled runtime, then config, then bare libllama.dylib.

Step 2 — macOS Contents/Resources/runtime

BundledRuntimes.directories gains macOsBundle:. It reads <exeDir>/runtime always and <exeDir>/../Resources/runtime when set. Roots are deduplicated and sorted reverse path order, so Contents/Resources/runtime deterministically wins over Contents/MacOS/runtime. Unreadable roots and entries are still skipped. LlamaLibraryLocator and ServerBinaryLocatorImpl enable the extra root when the detected OS is macos.

Resulting macOS resolution order: config runtime (selected first, then newest) → <exeDir>/lib/<exeDir>//<exeDir>/../Frameworks → bundled Contents/Resources/runtime before Contents/MacOS/runtime → config lib → bare libllama.dylib.

Step 3 — macOS release build runs outside the App Sandbox

com.apple.security.app-sandbox is removed from both Release.entitlements and DebugProfile.entitlements; the remaining DebugProfile entitlements (cs.allow-jit, network.server) stay. A comment in each file records why: LlamaPulse supervises an external llama-server process and opens .gguf files the user picks anywhere on disk, and Mac App Store distribution is out of scope. No other entitlement or Info.plist key changes — macos/Runner/Info.plist has no sandbox key, and there is no Runner.entitlements variant.

Step 4 — cross-platform packaging skeleton

New, under packages/llama_pulse_core/tool/:

  • package_app.dart — ArgParser-based entry point. Steps 5–6 wire it to the staging and archive writer; exit 0 success/--help/--check, 1 a missing bundle or runtime or a layout/archive problem, 2 usage or validation error.
  • packaging/package_target.dart — PackageTarget { linux, windows, macos } with executable names, archive kind (tar.gz on Linux, zip elsewhere), default Flutter bundle directory per target, artifactId, runtime-platform mapping and the runtime directory inside the stage (Contents/Resources/runtime/... on macOS).
  • packaging/package_plan.dart — pubspec version parsing (drops +build, rejects malformed values), stage/archive names, archFromAbi() via dart:ffi, repo-root discovery from Platform.script, exact required entries per target and verifyLayout.
  • packaging/process_runner.dart — ProcessRunner/SystemProcessRunner seam and flutter.bat on Windows.

Frozen CLI surface:

--os linux|windows|macos   (default: host)
--runtime <tag> <backend>  (refused for macos-x64 / linux-arm64, which have no pinned runtime asset)
--config-dir <dir>
--out <dir>                (default: <repo>/dist)
--bundle <dir>             (default: the target's Flutter release output)
--skip-build
--check
--help

Stage and archive names are version-derived and Linux reproduces today's name exactly:

Target Stage Archive
Linux x64 llama-pulse-0.1.0-linux-x64 llama-pulse-0.1.0-linux-x64.tar.gz
Windows x64 llama-pulse-0.1.0-windows-x64 llama-pulse-0.1.0-windows-x64.zip
macOS arm64 llama-pulse-0.1.0-macos-arm64 llama-pulse-0.1.0-macos-arm64.zip
macOS x64 llama-pulse-0.1.0-macos-x64 llama-pulse-0.1.0-macos-x64.zip

Still a no-op by design: staging the Flutter output, copying a managed runtime, writing archives and wiring CI are steps 5–8 in a later PR with the same frozen surface.

Dependency note

args moves from the CLI's runtime dependency into core's dev_dependencies only; the CLI keeps it too. It is already in the workspace dependency graph, so no new package enters the resolution and pubspec.lock stays untracked. archive, path and test were already used by core; no other dependency changes.

Step 5 — staging

packaging/staging.dart implements stage(...):

  • Copies the Flutter bundle recursively, preserving modes and symlinks. Windows drops *.pdb, *.exp and *.lib; macOS copies llama_pulse.app as one unit, so the framework symlinks under Contents/Frameworks stay symlinks.
  • Copies README.md and LICENSE beside the bundle: stage root on Linux/Windows, beside the .app on macOS, never inside Contents/.
  • Optional runtime: validates tag (^[A-Za-z0-9._-]+$, no ..) and backend (^[a-z0-9]+$), requires llama-server/llama-server.exe and runtime.yaml, checks the pinned v0.8.0 backend against RuntimeRelease.v080, and copies the tree verbatim to runtime/<tag>/<backend> (macOS: Contents/Resources/runtime/...). Rejections list the valid backends for the platform.
  • Refreshes the ad-hoc signature with codesign --force --deep --sign - <app> after adding resources.
  • Runs flutter build <os> --release in apps/llama_pulse unless --skip-build (flutter.bat on Windows).
  • verifyLayout now reports wrong-type entries as well as missing ones, one problem per entry.

Step 6 — archives

packaging/archive_writer.dart:

  • Linux: tar -C <out> -czf <archive> <stage> (system tar keeps the executable bit), then tar -tzf verification.
  • Windows: pure-Dart package:archive ZIP over the stage with / separators; verification re-opens it with ZipDecoder and checks the stage prefix and required entries.
  • macOS: ditto -c -k --sequesterRsrc --keepParent <stage> <archive>; verification extracts with ditto -x -k into a temp dir, checks the executable bit and runs codesign --verify --deep --strict on a macOS host.
  • package_app.dart now builds, stages, archives and verifies, printing created <archive> (size, verified) plus a run hint. --check stages into a temporary directory and verifies it without writing an archive. Any layout or archive problem exits 1 with a readable message.

Step 7 — Linux shim and melos scripts

  • tool/package_linux.sh is now a thin compatibility shim: it resolves the repo root from its own location, cds there and execs dart run packages/llama_pulse_core/tool/package_app.dart --os linux "$@". Arguments and the exit code pass through, so usage errors still exit 2 and --help prints the tool help.

  • The workspace pubspec gains cross-shell scripts with no cd and no $MELOS_ROOT_PATH:

    package:linux    dart run packages/llama_pulse_core/tool/package_app.dart --os linux
    package:windows  dart run packages/llama_pulse_core/tool/package_app.dart --os windows
    package:macos    dart run packages/llama_pulse_core/tool/package_app.dart --os macos
    
  • Verified on Linux: tool/package_linux.sh --skip-build --bundle apps/llama_pulse/build/linux/x64/debug/bundle --out /tmp/opencode/dist-shim produced llama-pulse-0.1.0-linux-x64.tar.gz (31.0 MiB, verified) and tar -tzf lists llama-pulse-0.1.0-linux-x64/llama_pulse; --bad-flag exits 2; dart run melos run package:linux -- --skip-build --check stages and verifies.

  • One melos 8.10.0 limitation: melos run <script> -- --help cannot forward --help, because melos treats any --help argument as a request for its own help and loads an empty config (lib/src/command_runner.dart, _shouldUseEmptyConfig). Use tool/package_linux.sh --help or the direct dart run .../package_app.dart --help, which both print the tool help.

Step 8 — CI packaging jobs

A new package job, separate from gate and the test matrix (both untouched), runs a macos-latest/macos and a windows-latest/windows leg with the workflow's pinned Flutter (FLUTTER_VERSION 3.47.7). Each leg checks out, installs Flutter, resolves the workspace, runs flutter build <target> --release in apps/llama_pulse, runs the packager twice — --check for the staged layout, then without --check to write the archive — and verifies the archive natively:

  • Windows (shell: pwsh): Expand-Archive into dist/verify, then assert a llama_pulse.exe exists.
  • macOS: ditto -x -k into dist/verify, assert llama_pulse.app/Contents/MacOS/llama_pulse is executable and codesign --verify --deep --strict passes. A second step greps codesign -d --entitlements :- of the built app for com.apple.security.app-sandbox and fails if present.

actions/upload-artifact@v4 uploads dist/*.zip as llama-pulse-<target> with 7-day retention, gated on refs/heads/main pushes and workflow_dispatch. PackageTarget.defaultBundleDir already matches the Flutter release output for both targets (build/windows/x64/runner/Release, build/macos/Build/Products/Release/llama_pulse.app), so no Dart default changed.

Follow-up fixes from the first CI run

  • requiredEntries now looks for the macOS flutter_assets under Contents/Frameworks/App.framework/Resources/, where Flutter actually puts them; the first Package (macos) run failed on the old Contents/Resources path. The test fakes grow matching App.framework bundles and framework Info.plist files so codesign accepts the nested code.
  • Two Windows test failures were fixed too: _joinEntry splits POSIX-style entries before joining, so reported paths use the host separator throughout, and the Flutter launcher follows the host (flutter.bat on Windows) rather than the target.
  • One macOS supervisor test (a failed abort of a start fails start and keeps the child) failed once and passed on the next run; it is a timing test unrelated to packaging.

Step 9 — documentation

  • docs/packaging.md is restructured around the three targets: concrete trees for Linux, Windows and macOS; per-host requirements; the runtime/<tag>/<backend> (macOS Contents/Resources/runtime) placement; the exact backends per platform; the Dart tool's flags and exit codes; --check; tar.gz/zip and the ditto macOS archive; and the fact that runtime.yaml is copied, never generated.
  • It records why macOS runs unsandboxed, the ad-hoc re-sign after staging, ditto -x -k verification and the quarantine consequence, and states explicitly that signing/notarisation is out of scope.
  • A "what was verified where" section is honest about coverage: the Linux bundle was built and smoke-tested locally; the Windows/macOS bundles were built and smoke-tested only by CI on windows-latest/macos-latest; macOS archive correctness cannot be verified on the Linux workstation.
  • README.md gains a "Package it" table for the three targets, drops the "Windows/macOS bundles are not part of it yet" wording, updates the tool/ line and the measured test count.
  • AGENT.md gains the package:linux|windows|macos commands and the direct Dart entry point (--skip-build --check), the new package CI job with artifacts only on main/workflow_dispatch, the packaging definition-of-done item, the Windows/macOS CI-only note, and the args dev-dependency justification.
  • docs/status.md is refreshed to this branch's baseline and measured counts and gains the Windows/macOS packaging entry, landmines and the remaining plan.
  • Issue chore(packaging): Windows and macOS desktop bundles #39's first nine checklist items are ticked; its last two (the ServerBinaryLocatorImpl pathContext item and CI verification of a runtime-bearing macOS bundle) stay open. The pinned checkpoint Project status & roadmap (checkpoint 2026-11-09) #16 is refreshed after merge, not in this PR.

Remaining for the operator

Review fixes

  1. CI sandbox assertion is now mandatory. The macOS step asserts the built .app exists and fails if codesign -d --entitlements :- cannot read it; only the grep for com.apple.security.app-sandbox remains conditional. Flutter's ad-hoc app with no sandbox entitlement exits 0 and prints nothing, so the step still passes on 0.8.0.
  2. Backend validation now precedes the install check. runtimeSelectionProblems in staging.dart resolves the tag through RuntimeRelease.forTag and validates the backend against it; runPackageApp calls it before touching the filesystem. An unknown tag (v0.7.0) exits 2 with "unknown runtime tag v0.7.0; this build packages v0.8.0", an unknown (cda13) or unpublished (metal on Windows) backend exits 2 and lists the valid backends; a valid but missing install still exits 1 with "install the runtime first". A stage path inside --bundle is refused (exit 2) before the install check, so the documented matrix holds even when both are wrong. docs/packaging.md's exit-code matrix matches.
  3. macOS runtime-bearing bundles are documented as untested. CI packages runtime-free bundles on both hosts; whether codesign --force --deep signs the runtime's dylibs under Contents/Resources and --verify --deep --strict then passes is unproven. docs/packaging.md says so, and issue chore(packaging): Windows and macOS desktop bundles #39 carries a checklist item for it.
  4. Windows ZIP is streamed. ZipFileEncoder/InputFileStream write each file incrementally and ZipDecoder().decodeStream(...) verifies from the file's central directory instead of readAsBytesSync. A CUDA-sized bundle no longer sits in memory in one piece, addFile closes each entry's stream, and a failed write deletes the truncated archive. The installed archive 4.6.0 supports both APIs cleanly, so this is the real fix, not a documented ceiling.
  5. Environment errors are caught. runPackageApp catches FileSystemException (create/delete/copy/chmod, unwritable/occupied --out), prints error: … and returns 1. The archFromAbi UnsupportedError is handled where it is raised (exit 2 for an unsupported ABI), so an unrelated UnsupportedError from a later call is no longer swallowed as a usage error. Tests occupy --out with a file.
  6. Path hardening. Empty --bundle/--out exit 2; a stage path inside --bundle (including equality) exits 2; a --bundle symlink to a directory is resolved before copying; the shim's cd to the repo root is documented so relative paths anchor there.
  7. Test weaknesses fixed. _verifyDitto now runs verifyLayout on any host and the executable-bit check on every POSIX host (Windows has no executable bit); only codesign is still gated on macOS, and the real-ditto test stays macOS-only. The Linux verifier test carries a comment explaining that its tar -tzf listing is synthesised from requiredEntries, with the Linux gate job covering the real-tar gap.

Review follow-ups (second pass)

  1. Docs corrections. docs/status.md no longer claims CI verifies the Contents/Resources runtime placement: CI packages runtime-free bundles, so only that layout and the ad-hoc signature are CI-covered; the placement is unit-tested pending issue chore(packaging): Windows and macOS desktop bundles #39. Both open chore(packaging): Windows and macOS desktop bundles #39 checklist items are named in the checkpoint and the remaining plan, and issue chore(packaging): Windows and macOS desktop bundles #39's footer note says items 10–11.
  2. Non-pinned tags are rejected. RuntimeRelease gained releases/forTag, so runtimeSelectionProblems resolves the tag instead of comparing to v080; a second pin cannot silently skip validation. Updated the tests and docs/packaging.md.
  3. Usage-error precedence. The stage-inside-bundle check runs before the runtime-install check, so a nested --out plus a missing runtime exits 2 as documented.
  4. Narrowed catch. The UnsupportedError catch around the staging/archive block is gone; only the archFromAbi call keeps its own handler.
  5. ZIP handle hygiene. _writeZip is async and uses addFile, which closes the per-entry InputFileStream; a failed write deletes the truncated archive instead of leaving it at the final path. Streaming (no whole-archive buffering) is unchanged.
  6. New tests. runPackageApp exit 1 for an installed-but-missing runtime (cuda13) with the install hint; unknown tag (v0.7.0) exits 2; a nested --out beats a missing runtime; a zero-byte and a 5 MiB ZIP entry round-trip; RuntimeRelease.forTag resolves the pin and rejects unknown tags.

Tests

  • library_locator_test.dart: Windows context candidates, macOS posix candidates, a macOS bundle library under Contents/Resources resolves, and a config runtime still wins over it.
  • bundled_runtimes_test.dart: Contents/Resources is listed only with macOsBundle: true, Resources sorts before MacOS, and the resources root is computed correctly.
  • server_binary_locator_test.dart: a bundled llama-server under Contents/Resources/runtime/... is located (existing fake version reader).
  • test/tool/packaging/package_target_test.dart: executable names, archive kinds, default bundle dirs, runtime-platform/artifact mapping and runtime stage dirs.
  • test/tool/packaging/package_plan_test.dart: version parsing including +build and malformed input, all stage/archive names, exact required entries per target, runtime entries, verifyLayout success/failure, ABI mapping, repo-root discovery.
  • test/domain/runtime/runtime_release_test.dart: the v0.8.0 pin (tag, build, commit, digests, sizes), per-platform backend coverage and archive names, releases/forTag (the pin resolves, an unknown tag is null), CUDA cudart archives, and platform naming.
  • test/tool/packaging/package_app_test.dart: --help exits 0 and lists every flag; unknown flag and missing --runtime value exit 2; --skip-build with a nonexistent bundle exits 1 with no writes; --runtime refusal on macos-x64/linux-arm64; an unknown tag (v0.7.0), an unknown backend (cda13) and an unpublished backend (metal on Windows) exit 2 before the install check and list the valid options; a published but missing runtime (cuda13) exits 1 with the install hint; empty --bundle/--out and a stage inside the bundle exit 2, the latter even when the runtime is also missing; an occupied --out exits 1 with error:; a symlinked --bundle is resolved; --check stages, verifies and creates no output directory; a non-check Windows run writes and verifies the ZIP; a layout failure exits 1.
  • test/tool/packaging/staging_test.dart: every target layout stages and verifies clean; .pdb/.exp/.lib are excluded; README/LICENSE placement; framework symlinks survive; the runtime lands at the documented path; failure modes (missing bundle, missing server binary, missing runtime.yaml, .. tag, unknown tag, unknown backend, metal on Windows, cuda13 on macOS, macos-x64); verifyLayout flags each deleted entry; the exact flutter build <os> --release command and the macOS-only codesign call.
  • test/tool/packaging/archive_writer_test.dart: streamed Windows ZIP round-trip with a single stage prefix, plus a zero-byte and a 5 MiB entry that survive byte for byte; exact Linux tar -C/-tzf and macOS ditto -c/-x argv; the ditto fake now verifies the extracted layout and executable bit on any host; a real ditto archive test guarded by Platform.isMacOS.

Verification

  • cd packages/llama_pulse_core && dart test test/tool/packaging → 78 passed, 1 skipped (the real macOS archive test).
  • dart run packages/llama_pulse_core/tool/package_app.dart --help → exit 0.
  • --os windows --skip-build --bundle /tmp/opencode/missing → clear error, exit 1, no path created.
  • dart run melos analyze, dart run melos run format:check, dart run melos run gen twice (0 outputs written), dart run melos run gen:catalog:check and full dart run melos run check are green.
  • Real Linux smoke on this host: dart run packages/llama_pulse_core/tool/package_app.dart --os linux --skip-build --bundle apps/llama_pulse/build/linux/x64/release/bundle --out /tmp/opencode/dist-smoke produced llama-pulse-0.1.0-linux-x64.tar.gz (10.2 MiB, verified). tar -tzf lists the expected prefix, extraction succeeds and llama_pulse is executable.
  • Measured suite counts after merging origin/main (which brought PR fix(runtime): download the pinned archives from the nightly release #38's new runtime-asset tests): core 892 passed (+2 skipped), CLI 233, app 166 — 1291 tests. The packaging suite alone is 78 passed (+1 skipped). The extra skip is the real-ditto archive test.
  • CI is expected to run seven jobs: gate, the test matrix (Linux, macOS, Windows), the standalone Windows CLI compile, and Package (macOS)/Package (Windows).

Merge with main (runtime asset URLs, PR #38)

origin/main moved to c72c5ec (the PR #38 merge) after this branch was cut, so the branch merges it in as 8549e0e.

Refs #39

LlamaLibraryLocator accepts an optional package:path context so Windows and macOS path shapes can be unit-tested on any host. Production keeps the host context; only the tests pass a foreign one.
BundledRuntimes.directories accepts macOsBundle and reads Contents/Resources/runtime as well as Contents/MacOS/runtime. Reverse path order makes Resources win deterministically; the library and llama-server locators enable it when the detected platform is macOS.
Create the fixture as llama-server explicitly, since the macOS locator never looks for the .exe name, and skip the test on Windows where the POSIX executability check cannot pass. The path logic stays covered there by the BundledRuntimes and library-locator tests.

Thread p.Context through BundledRuntimes so a foreign path context really does render the bundled runtime roots, and pin the Contents/Resources over Contents/MacOS precedence at both locator levels.
Name <exeDir>/runtime in the resolution order and the macOS Contents/Resources root with its precedence over Contents/MacOS.
@Dimoshka Dimoshka changed the title feat(core): path context and macOS bundled runtimes feat: Windows/macOS packaging groundwork Nov 9, 2026
tool/package_linux.sh resolves the repo root, changes there and execs
package_app.dart with --os linux, forwarding arguments and the exit code
(usage errors still exit 2). The melos workspace gains package:linux,
package:windows and package:macos scripts so each platform builds through
the same Dart tool without a cd or MELOS_ROOT_PATH.
A new package job builds the desktop app for its target, checks the
staged layout with --check, writes the archive, then verifies it the way a
user would: Expand-Archive plus llama_pulse.exe on Windows, ditto plus an
executable main binary and a strict codesign check on macOS. The macOS
matrix entry also asserts the built app is not sandboxed. Archives upload
on main and workflow_dispatch only; the gate and test matrix are
untouched.
The new CI package job found that a Flutter macOS release app keeps
flutter_assets under Contents/Frameworks/App.framework, not Contents/
Resources, so the required entries now point at that path (verifyLayout
follows the framework's Resources symlink). The macOS test fakes grow the
App.framework and the Info.plist files codesign needs for nested
frameworks, which also stops the real ditto test from failing at signing.

Two pre-existing Windows test failures are fixed too: _joinEntry splits
POSIX-style entries before joining so reported paths use the host
separator throughout, and the Flutter launcher follows the host rather
than the target, since a Windows host has flutter.bat.
Both expectations assumed a POSIX host: the build test pinned the bare
flutter launcher, and the deleted-entry test joined a POSIX-style required
entry without splitting it. They now accept flutter.bat and use host
separators, so the Windows test matrix runs the packaging suite.
Factor the pinned-release backend lookup into runtimeSelectionProblems and call it in runPackageApp before the filesystem check, so a typo or an unpublished backend exits 2 with the valid list instead of reporting a missing install.
Reject empty --bundle/--out, refuse a stage inside the bundle, resolve a symlinked bundle root, and turn FileSystemException/UnsupportedError into error: lines with exit 1 (2 for an unsupported ABI). Tests cover an occupied --out, the nested stage, empty paths and a symlinked bundle.
The entitlement read no longer ignores a missing, mispathed or unsigned app: the step asserts the built app exists and fails when codesign cannot read it. Only the grep for com.apple.security.app-sandbox stays conditional.
Run verifyLayout and the executable-bit check after ditto extraction on every host and gate only codesign on macOS. Stream Windows ZIP writes and verification so a CUDA-sized bundle is not buffered whole, and explain the synthesised tar listing in the Linux test.
Make the exit-code table true after the backend-validation change, state that CI packages runtime-free bundles and that a runtime-bearing macOS bundle is unverified, attribute the earlier Linux GUI check to PR #28, and fix the host/env-gated skip wording.
Windows reports a synthetic file mode whose bits are not POSIX permissions, so the extracted macOS executable was flagged on the Windows test runner. Keep the layout check and the POSIX executable-bit check; skip only the bit on Windows, which has no executable bit.
The packager skipped backend validation for any tag other than v0.8.0, so `--runtime v0.7.0 bogus` fell through to the install check and exited 1. Resolve the release by tag through a new RuntimeRelease.forTag/releases seam (a second pin cannot silently bypass validation), reject an unknown tag with exit 2, move the stage-inside-bundle check above the install check so the documented exit-code matrix holds, and drop the now-unreachable UnsupportedError catch.
ZipFileEncoder.addFileSync opens an InputFileStream per entry and never closes it, so a runtime-bearing Windows bundle could exhaust file handles. Use the async addFile, which closes the stream, delete the archive when the write fails instead of leaving a truncated file at the final path, and cover a zero-byte plus a 5 MiB entry round-trip.
CI packages runtime-free bundles, so only that layout and the ad-hoc signature are CI-verified; the Contents/Resources runtime placement is unit-tested only. Name both open #39 checklist items (the pathContext seam and the runtime-bearing CI bundle), record the unknown-tag and usage-error precedence in docs/packaging.md, and refresh the measured counts to 1288 (889 core plus two skips, 233 CLI, 166 app).
…ag + pin)

Integrates the runtime archive fix and the build-script commit check from
PR #38 after it merged into main.

Conflicts resolved:
- runtime_release.dart: keep RuntimeRelease.releases/forTag from this branch
  and assetTag on every asset from main. tag stays v0.8.0 (install identity),
  assetTag is b11960 (download release).
- runtime_release_test.dart and docs/runtime.md merged cleanly and now cover
  both sides: nightly URLs plus releases/forTag, and the bundled-runtime
  resolution order.

docs/status.md, AGENT.md and README.md record 1291 tests (892 core plus two
host/env-gated skips, 233 CLI, 166 app) measured on this merge.
@Dimoshka
Dimoshka merged commit c723c86 into main Nov 11, 2026
7 checks passed
@Dimoshka
Dimoshka deleted the chore/packaging-windows-macos branch November 11, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant