Omit `checks`, `validation`, `build`, and `delivery`. Their omission selects
the upstream defaults without installing extra gates or deliverables.
2. Write `scripts/release` with `#!/bin/sh` and `set -eu`. Require exactly one
argument; otherwise print `usage: ./scripts/release VERSION` to stderr and
exit 2 before invoking Go. Pass the version argument unchanged to ReleaseKit;
ReleaseKit owns semantic version validation.
3. Resolve the repository root from the launcher's directory, then change to
that root. Use quoted path expansions and a task-specific variable such as
`commandkit_root`. Resolve the directory with `CDPATH=` for the directory
change so an ambient `CDPATH` does not corrupt captured path output. Support
ordinary invocation from another working directory and paths containing
spaces; no general symbolic-link resolution framework is required.
4. Create `.releasekit/bin`. Query Go's `GOHOSTOS` and `GOHOSTARCH` using the
local toolchain with workspace discovery disabled, storing them separately
without replacing the version argument. Build the tool using those native
target values, `GOWORK=off`, `GOTOOLCHAIN=local`, and this argument vector:
```text
go build -mod=readonly -o .releasekit/bin/releasekit gitea.maximumdirect.net/eric/ReleaseKit/cmd/releasekit
```
This builds the executable for the host that must run it, even when the
caller has selected an application cross-compilation target. Dependency
preparation remains separate; no launcher `go get`, `go mod tidy`, alternate
module file, linker injection, or independently selected tool version is
needed. Bootstrap can use normal module access; the candidate suite itself
is offline under ReleaseKit's environment.
5. Replace the shell process with the built executable using `exec` and this
argument vector, quoting both absolute paths and the version:
```text
.releasekit/bin/releasekit publish --repo ROOT --config ROOT/releasekit.json VERSION
```
The flags precede `VERSION` as required by ReleaseKit. Preserve its streams
and exit status directly. Do not intercept its publication states, run `check`
first, auto-push `main`, or use `go run` as the publication process.
6. Mark the launcher executable in Git. Add `/.releasekit/` to the root ignore
file; this local bootstrap output is not a declared release artifact.
**Evidence:** review the profile against upstream, run `sh -n scripts/release`,
and verify the executable mode and ignore rule. Exercise zero and multiple
arguments without a candidate; both return 2 without tool bootstrap. Do not
invoke a valid version against CommandKit's real repository as a smoke check.
**Completion:** the profile and launcher match the selected target and delegate
publication to the module-selected native CLI. Commit the stage.
## Stage 3: cover launcher behavior with fast offline tests
**Status:** pending. **Prerequisite:** Stage 2.
**Files:** `scripts/release_test.go`, plus launcher corrections found by the
tests and stage evidence in this plan. Use a tests-only external Go package such
as `release_test`; add no production Go file or exported helper API.
1. Use standard-library Go tests with temporary directories. Copy the real
launcher and profile into a temporary repository-shaped directory whose path
contains spaces. Invoke the copied launcher from a different directory.
A controlled `go` executable placed first on the child process's `PATH`
supplies the two host environment answers and writes a fake executable at
the requested build output path. The fake executable logs received arguments,
writes distinct stdout/stderr messages, and exits with a test-selected status.
Keep all test controls inside fixture subprocess environments, not launcher
options or product configuration.
2. Cover these observable integration risks:
| Case | Required observation |
| --- | --- |
| Zero or multiple arguments | Usage on stderr and status 2; Go is not invoked. |
| Valid invocation from another directory | Build occurs at the copied root; publication receives `publish`, explicit absolute repository/profile paths, and the supplied version as separate arguments. |
| Module-selected native bootstrap | The tool package has no `@version` suffix; readonly mode and the expected output are used; the local toolchain, disabled workspace, and queried host target govern the build. |
| Executable returns 0, 1, 2, or 3 | Each status is preserved, with stdout/stderr delivered to their corresponding streams. These cases test delegation, not upstream recovery semantics. |
| Bootstrap failure | The launcher stops with the build failure and never invokes the fake publication executable. |
3. Test argument values and outcomes rather than exact shell source or private
call counts. Do not add a signal-management framework or a new guarantee
merely to test `exec`; source review plus direct process replacement establish
the selected signal handoff. The script remains a POSIX maintainer tool:
skip shell execution tests with an explicit reason if a POSIX shell is
unavailable, rather than add a Windows publisher.
4. Use per-command working directories and environments. Do not modify process
global working directories or the real repository's remote configuration.
**Evidence:** format the test file, run the focused scripts package tests, then
the default offline suite with the Stage 1 environment. None of these tests
compiles a real publisher or contacts a service.
**Completion:** the small launcher has regression coverage for its own parsing,
path, build, and delegation responsibilities. Commit the stage.
## Stage 4: exercise real publication against disposable Git repositories
**Status:** pending. **Prerequisite:** Stage 3 and prepared dependency caches.
**Files:** `scripts/release_integration_test.go`, supporting fixture helpers in
that tests-only package if needed, and stage evidence in this plan.
1. Add `TestReleaseIntegration`, skipped unless
`COMMANDKIT_RELEASE_INTEGRATION=1`. This explicit development opt-in is not
a profile check or a required step before ordinary releases. Keep fast tests
enabled normally. Explain the opt-in and cache prerequisite in the test;
Stage 5 exposes its command in the developer guide.
2. Construct a disposable worktree on `main` and a separate local bare Git
repository. Supply fixture-local identity and disable signing and hooks
through isolated Git configuration; do not alter the maintainer's global
settings. Clear inherited Git directory/worktree overrides for fixture
commands so every operation addresses the fixture.
3. Copy CommandKit's current `go.mod`, `go.sum`, launcher, and ignore file into
the fixture. Copy the real JSON profile, changing only `expected_push_url`
to the local bare repository's absolute path. Keep product `CommandKit` and
remote `origin`. Do not copy CommandKit's tests: the fixture is a tiny Go
package, avoiding recursive execution of these integration tests.
4. Write a fixture-only note `docs/releases/v0.0.1.md` headed
`# CommandKit v0.0.1`, and a small standard-library Go test that appends one
marker to a path supplied through its environment outside the worktree.
This marker demonstrates one candidate-suite run without adding a product
check. Commit fixture contents, configure `origin` to the local bare path,
and push fixture `HEAD:refs/heads/main`. These are local fixture operations,
not publication of CommandKit.
5. Invoke the copied launcher from another directory with the actual Go tool
and module-selected ReleaseKit. Make dependency and toolchain downloads
unavailable during the exercise. Check that it returns success, reports the
fixture candidate/version, publishes an annotated `v0.0.1` tag pointing at
that commit, leaves remote `main` at the candidate, writes one suite marker,
and leaves fixture candidate status clean. Check that only the intended tag
was added and no binary delivery outputs were produced. The ignored local
bootstrap executable is expected.
6. On that fixture, prepare and commit a second note for `v0.0.2` and change
`expected_push_url` to a different local path. Push the new fixture `main`,
then invoke the launcher. Expect known failure status 1 with an endpoint
mismatch diagnostic, no new local or remote tag, and no additional suite
marker. This protects profile wiring without rebuilding upstream's entire
remote-conflict and failed-push matrix. Status 3 propagation is already
covered by the Stage 3 executable double.
7. Use `t.TempDir` cleanup and an overall subprocess context deadline to avoid a
stalled fixture. Avoid timing-sensitive assertions and output snapshots.
Missing shell/Git support can cause an explicit platform skip; when opt-in is
selected, missing dependency caches or a real execution failure must be
reported as failure, not disguised as a successful skip.
**Evidence:** prepare caches using the Stage 1 preparation command, then run:
```sh
COMMANDKIT_RELEASE_INTEGRATION=1 GOWORK=off GOTOOLCHAIN=local GOPROXY=off GONOPROXY=none GOSUMDB=off go test -mod=readonly -count=1 ./scripts -run '^TestReleaseIntegration$'