# Implementation plan: host-native ReleaseKit launchers Follow the stages in order as one sprint. The [feature roadmap](host-native-launchers.md) defines the intended behavior and scope. The focused checks below describe regression coverage; intermediate stages need no separate review, release, or passing build. Run the assembled checks in Stage 6. ## Stage 1: Normalize the local multi-module launcher **Completed.** 1. In [the multi-module launcher](../../examples/multi-module/release.sh), after entering the selected module and exporting its existing `GOWORK=off` and `GOTOOLCHAIN=local` settings, insert the following before the first Go bootstrap command. Reuse this shell recipe in the subsequent stages: ```sh releasekit_host_os=$(go env GOHOSTOS) || exit $? releasekit_host_arch=$(go env GOHOSTARCH) || exit $? export GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" ``` Keep the checked assignments separate from `export` so a failed Go query stops the launcher. Keep host selection inside the existing launcher scope; preserve selected module metadata, dependency preparation, publication arguments, traps, and the child status. Application targets still come from release profiles. 2. Extend [the existing launcher tests](../../cmd/releasekit/multi_module_example_test.go). In `TestMultiModuleLauncherUsesSelectedModuleAndProfile`, let the Go shim answer the two host queries and assert the exported target at `go list -deps` in the selected module. In `TestMultiModuleLauncherRunsToolFromSelectedDependency`, supply a foreign target to the launcher and retain real Go tool preparation and execution. Keep the checks for the nested module's version, forwarded profile and arguments, statuses 0 through 3, unchanged source, and bootstrap cleanup. For these fixtures, choose `windows` unless the host OS is `windows` (then `linux`), and `arm64` unless the host architecture is `arm64` (then `amd64`). This makes both variables differ from the host pair. Keep synthetic versions in a test-owned `GOMODCACHE`. ## Stage 2: Normalize consumer Woodpecker templates **Completed.** 1. Apply the Stage 1 recipe in the [single-module](../../examples/woodpecker/tag-delivery.yml) and [multi-module](../../examples/multi-module/woodpecker.yml) tag delivery commands, under the existing Go settings and in the selected module. Insert it after tag verification and checkout, before the first Go bootstrap command. Write the new shell variable references as `$${releasekit_host_os}` and `$${releasekit_host_arch}` so Woodpecker passes them through. Keep the verified commit, selected tool, profile mapping, credentials, alternate module and sum files, cleanup, and `ci-deliver --prepared` invocation. 2. Update the existing [single-module](../../cmd/releasekit/woodpecker_test.go) and [multi-module](../../cmd/releasekit/multi_module_woodpecker_test.go) workflow harnesses. Teach the controlled Go shim to answer host queries before enforcing its alternate module file checks. In both Go shims, log the target at `go list -deps`. Feed a foreign target to `TestWoodpeckerTagDeliveryTemplate`, `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion`, and `TestMultiModuleWoodpeckerDelivery` and check that dependency inspection uses the host pair. Retain real execution where the fixture already uses it, and keep the existing checks that a rejected tag or commit reaches no Go command. ## Stage 3: Normalize source-built launchers **Completed.** 1. Apply the same recipe in the [maintainer launcher](../../scripts/release) before building and executing ReleaseKit. Prefix the two queries with `GOTOOLCHAIN=local` to match its existing build. Preserve usage checking, output location, build settings, and the final `exec`. 2. Apply the recipe in the [source delivery template](../../examples/woodpecker/releasekit-source-delivery.yml) after tag verification and checkout, under its existing Go settings and before the source build. Use the Woodpecker escaping from Stage 2. Keep its temporary build output, dependency preparation through the existing build, and prepared delivery arguments. Exercise `TestWoodpeckerSourceDeliveryTemplateSelfBootstraps` with a foreign target; log and assert the host pair at the real `go build`, retain actual execution, and keep the checkout, cleanup, and dependency preparation assertions. 3. Update the manual delivery retry in the [maintainer runbook](../release.md). After fetching and checking out the tagged version, run host selection, dependency download, build, and delivery inside a subshell so the caller's target environment stays intact. Query with `GOTOOLCHAIN=local` as above; make the sequence stop on failure inside the subshell. Preserve the dependency download before the offline build, the build-only proxy and sum settings, the output path, and the older-tag credential example. ## Stage 4: Normalize module-based recovery preparation **Completed.** The CLI adapter and assembled handoff tests follow in the next stage. 1. In [the Go recovery implementation](../../internal/gotool/recovery.go), keep the existing registered-tool, API-only, and no-dependency selections. Have `RecoveryTool` return `(*RecoveryInvocation, error)` within the internal package. Give the descriptor `Directory string` and `Args []string` fields. A nil result still selects the native executable fallback. Set the directory to the original module for a registered tool or the durable bootstrap root for an API-only consumer. Put `tool -modfile=` and the existing full command package path in `Args`; the caller supplies the working directory. Preserve the selected version, local and versioned replacements, private module and sum files, and their existing lifetime. 2. Immediately before the API-only branch runs `go list -deps`, query `go env -json GOHOSTOS GOHOSTARCH` in its private root with its existing `GOWORK=off` and `GOTOOLCHAIN=local` overrides. Reuse the internal `discover` runner with the existing context. Decode both fields, treat missing values as a preparation error, and add only `GOOS` and `GOARCH` to the environment of the dependency command. Keep the target-independent module-selection query and the registered-tool early return. Use the existing preparation error and cleanup paths; leave candidate Go settings, proxy policy, and fallback selection as they are. 3. Update the direct `RecoveryTool` callers in [the handoff tests](../../cmd/releasekit/publication_handoff_test.go) to run the descriptor's arguments in its directory. In `TestRecoveryToolPreservesVersionedReplacement`, set a foreign target for the preparation call, log the target of the real `go list -deps` through a delegating shim, and verify the host pair there. Run the test's subsequent direct tool invocation with the host pair and retain its version and metadata assertions. Retain the source-module and vendored API-only cases. Update the CLI caller in Stage 5. ## Stage 5: Normalize the printed recovery command when it runs **Completed.** 1. In `selectedPublicationRecoveryCommand` in [the CLI](../../cmd/releasekit/main.go), render the descriptor as one copyable POSIX shell command. Use this structure, with ``, ``, and `` standing for shell-quoted operands: ```sh (export GOWORK=off GOTOOLCHAIN=local && releasekit_host_os=$(go -C env GOHOSTOS) && releasekit_host_arch=$(go -C env GOHOSTARCH) && GOOS="$releasekit_host_os" GOARCH="$releasekit_host_arch" go -C tool '-modfile=' reconcile --record ) ``` Build the common `go -C` prefix from the descriptor directory and render its tool arguments using the existing diagnostic redaction and shell quoting. Keep the explicit absolute record operand. Both host queries run in the invocation directory with the same Go settings as the final command. A failed query stops the command, and the subshell returns the final command status when launched. Leave the native executable command, handoff timeout and cancellation behavior, and existing fallback and diagnostic behavior intact. 2. In `TestColdGoToolRecoverySurvivesLauncherExit`, execute the printed command in a separate shell with a foreign target, after the original launcher and its temporary files are gone. Exercise both registered and API-only handoffs and retain status checks for 0 through 3. For the alternate-record cases, replace the quoted original record operand inside the command instead of appending flags after the closing subshell. Retain the vendored API-only, native fallback, record-selection, and redaction checks. Exercise the printed command itself; running the Go descriptor directly would miss a regression in the shell recipe. ## Stage 6: Align consumer guidance and verify the sprint 1. In the [adoption guide](../consumers/adoption.md), make the minimal launcher use the Stage 1 recipe followed by `exec go tool releasekit "$@"`. Run it from the selected module, retaining that consumer's workspace and toolchain settings. Move the registration smoke check to follow the launcher example and invoke `version` through that launcher. In the [multi-module README](../../examples/multi-module/README.md), apply the same recipe to the direct `check` smoke commands: enter each selected module in its own subshell, select the host there, then run the existing check with its repository-root profile and version. Preserve the source-selected fixture instructions and avoid turning the smoke check into publication. 2. Align the [consumer delivery guide](../consumers/delivery.md), [CLI contract](../cli.md), and [internal Go tool guide](../internal/gotool.md) with the maintained recipes and handoff behavior. Explain that consumers update copied launchers and templates separately from upgrading the module dependency. ReleaseKit tests generic bootstrap behavior; consumers can focus their own wiring tests on selectors and profile mapping. Profile build targets continue to select application binaries. 3. Run the focused launcher, workflow, and recovery fixtures from the preceding stages with the existing local Git and module proxy fixtures. Run `go test ./...` and `go run ./internal/apicheck` as documented in the [development guide](../development.md#validation). Check shell syntax, including Woodpecker command blocks after interpolation, local documentation links, and `git diff --check`. Use the existing offline fixtures; no hosted delivery, live publication, or external runner is needed. 4. Include the following entry in the next release note during release preparation, when its version is selected: “Maintained launchers and generated Go recovery commands now use the host target to prepare and run ReleaseKit. Update copied launchers and CI templates to get the fix; release profiles still select application build targets.”