# Release tooling implementation plan Status: Stages 1–6 complete. Follow the stages in integer order. This is one end-to-end sprint: intermediate stages may temporarily leave scripts, tests, and documentation inconsistent. Do not add compatibility scaffolding or repeat complete suites merely to make each intermediate stage independently releasable. Follow the local commit policy for completed, validated units; stage boundaries do not require commits. The [feature roadmap](release-simplification.md) owns the target and selected choices. Read [development guidance](../development.md), the [agent policy](../policy/agents.md), [testing policy](../policy/testing.md), [documentation policy](../policy/documentation.md), and the applicable [release policy](../policy/release.md) before implementation. Use codebase-memory tools for code discovery as instructed by `AGENTS.md`. ## Baseline and fixed decisions Planning baseline: `3d396b1a66bfafc509472cd74a3f8029def3d432`. At that revision: - `go.mod` registers ReleaseKit v0.8.0. The root module owns the selected tool version even when releasing an integration. Keep that dependency selection, all library module paths and versions, and Go minimums unchanged. - Three JSON profiles select core, ckconfig, and ckprompt. Their only file selection is the explicit root shared-file list; hosted delivery is absent. - `scripts/release` is a 49-line native-build launcher. The 506-line `scripts/release_test.go` and 399-line `scripts/release_integration_test.go` contain the test scaffolding being replaced. - `.releasekit/gitea-token` is a maintainer-supplied ignored credential file. Its contents are not input to implementation or tests. Preserve the file and directory ignore rule. Do not add the credential to fixtures, logs, or commits. - Both integrations declare core v0.2.0, while their current source requires newer APIs. Workspace-off Go 1.26.8 tests fail at baseline with missing `commandkit.CompletionHint`, `Explanation`, and other symbols. This is an existing release-readiness issue, not a reason to change dependency versions or weaken validation in this sprint. Stage 5 distinguishes this limitation from verification of the current source and selected file checks. The former open decisions are resolved for this plan: select all four proposed generic file checks and use host-normalized registered-tool execution. Add the agreed forbidden token path and local page-only Gitea delivery. Keep ordinary source publication and hosted delivery separate. No binary delivery, CI job, library API change, dependency upgrade, or production publication is part of this sprint. No implementation-blocking design question remains. ## Stage 1: Configure the three release profiles **Completed.** All three profiles parse as JSON and select the planned checks and delivery destination; the credential directory remains ignored. Edit `releasekit.json`, `integrations/ckconfig/releasekit.json`, and `integrations/ckprompt/releasekit.json`. 1. Preserve each profile's `product`, `module_dir`, `remote`, `expected_push_url`, and explicit `validation.shared_files`. The endpoint remains `ssh://git@forge.maximumdirect.net:22222/base-layer/commandkit.git`. 2. Add `validation.go_format: true`, `validation.markdown: {}`, and `validation.module_distribution` with `workspace_files` and `local_replacements` both true. 3. Add `validation.forbidden_files` containing only `.releasekit/gitea-token`. This is a literal repository-relative selection applying to the captured repository, including integration releases. 4. Add `delivery` with `forge: "gitea"`, `base_url: "https://forge.maximumdirect.net"`, `owner: "base-layer"`, `repository: "commandkit"`, and `token_file: ".releasekit/gitea-token"`. Leave `token_env` omitted, preserving ReleaseKit's default environment override. Add no build targets or additional validators. 5. Retain `/.releasekit/` in `.gitignore`; revise its comment from executable storage to local ReleaseKit credentials. Do not remove existing local files. Check that all three files parse as JSON. Use the pinned [configuration reference](https://forge.maximumdirect.net/base-layer/releasekit/src/commit/23203f235cc414c4ffe0fd3941030eb0c9f9943f/docs/config.md) for field semantics. Root file checks stop at independent module boundaries; the forbidden-path check has its separately documented repository scope. Do not create custom validators, broader forbidden lists, or blanket exclusions. **Stage result:** the intended checks and delivery destination are represented entirely by the existing upstream profile schema. ## Stage 2: Centralize tool invocation and module routing **Completed.** Both scripts pass shell syntax checks; a fake Go invocation confirmed representative publication and raw-command forwarding. Focused coverage follows in Stage 3. Create executable POSIX-shell `scripts/releasekit` as the shared entry point. Keep executable `scripts/release` as the existing publication convenience entry point. No shell library, module-map file, generated script, or JSON parser is needed in the launcher. The shared entry point has these forms: | Invocation | Handling | | --- | --- | | `./scripts/releasekit MODULE publish [ARGS...]` | Select module defaults and invoke upstream `publish`. | | `./scripts/releasekit MODULE check [ARGS...]` | Select module defaults and invoke upstream `check`. | | `./scripts/releasekit MODULE deliver [ARGS...]` | Select module defaults and invoke upstream `deliver`. | | `./scripts/releasekit version [ARGS...]` | Forward without module/profile inference. | | `./scripts/releasekit publication-attempts [ARGS...]` | Forward without module/profile inference. | | `./scripts/releasekit reconcile [ARGS...]` | Forward without module/profile inference. | `ARGS` are ordinary upstream arguments, including the version where required; forward them as separate quoted arguments, without shell evaluation. This wrapper covers the selected maintainer workflow; it does not reimplement the ReleaseKit parser. Let upstream validate versions, flags, records, and candidate state. Reject a missing selector/operation or unsupported wrapper form with usage on stderr and status 2 before running Go. Preserve meaningful argv boundaries; exact usage prose is not a compatibility target. Implement the only selector mapping in `scripts/releasekit`: | Selector | Module directory | Profile | Product for profile-wiring tests | | --- | --- | --- | --- | | `core` | `.` | `releasekit.json` | `CommandKit` | | `ckconfig` | `integrations/ckconfig` | `integrations/ckconfig/releasekit.json` | `CommandKit ckconfig` | | `ckprompt` | `integrations/ckprompt` | `integrations/ckprompt/releasekit.json` | `CommandKit ckprompt` | Implementation details: 1. Resolve the repository root from the script location using quoted POSIX shell operations safe with spaces and inherited `CDPATH`; change there. 2. Set `GOWORK=off` and `GOTOOLCHAIN=local` before querying Go. Resolve native `GOHOSTOS` and `GOHOSTARCH` through the selected `go`, then export them as `GOOS` and `GOARCH` for tool execution. Do not inherit a cross-build target. 3. Read the effective flags with `go env GOFLAGS`. Append `-mod=readonly` to that string and export it as `GOFLAGS`, preserving the other effective flags. Appending gives read-only selection precedence over an inherited `-mod` setting. Do not parse or evaluate the string in the shell. Go 1.26.8 does not accept `go tool -mod=readonly`; do not use that unsupported form. 4. For module operations, prepend `OPERATION --repo ROOT --config ABS_PROFILE`. For `publish` only, also prepend `--expected-module MODULE_DIR`. Neither ordinary `check` nor `deliver` accepts that publication flag. Append the supplied upstream arguments unchanged; upstream owns its flag semantics. 5. For the three raw forms, forward the command and supplied arguments without adding profile, module, or publication flags. The working directory is the repository root. Relative record paths therefore resolve there; document absolute record paths in examples for clarity, without prohibiting relative ones. Recovery identity comes from the selected saved record. 6. Finish through `exec go tool go.maximumdirect.net/releasekit/cmd/releasekit`. Preserve the child's output streams and exit status. Do not create or build an executable in `.releasekit/bin/`, preflight Gitea, or chain delivery after publication. Reduce `scripts/release` to checking its existing two-argument shape, then executing the sibling helper with `MODULE publish VERSION`. Delegate selector validation to that helper, avoiding a second mapping. Keep `./scripts/release MODULE VERSION` usable from outside the repository. **Stage result:** all selected operations share root-tool execution and one module mapping; publication and delivery remain explicitly separate commands. ## Stage 3: Replace release-test scaffolding with focused wiring tests **Completed.** The focused scripts suite passes with the subprocess fake; the retired publication exercise and its scaffolding have been removed. Rewrite `scripts/release_test.go` around the actual scripts and a small fake Go executable at the subprocess boundary. Remove `scripts/release_integration_test.go` and helpers with no remaining users. Use temporary directories, including a repository path with spaces and an outside invocation directory. Copy the scripts and actual profiles into the fixture. The fake Go needs only to answer the queried `go env` values and to record the tool invocation, selected environment values, and controlled output and status. Simple argument recording suffices: remove `goCall`, its bespoke record parser, native-build assertions, and tests of assertion helpers. Do not retain a fake build stage, fake publisher executable, Git repository fixture, tag inspector, suite marker, or release-note generator. Cover these behaviors with compact cases: - Check the selector table against the copied profiles' module/product and the selected destination, forbidden token path, shared files, and validation selections. This checks CommandKit's choices, not upstream parsing semantics. - Exercise each selector with `publish`, `check`, and `deliver`. Verify absolute repository/profile arguments, publication-only `--expected-module`, and unchanged forwarding of supplied upstream arguments. - Exercise the legacy publication entry point and raw recovery/version forms. Verify recovery receives the explicit record/options without inferred module identity or a profile argument. - Use foreign target values, workspace/toolchain overrides, and effective `GOFLAGS` containing an unrelated flag and `-mod=mod`. The recorded tool environment must select the host, disable workspace discovery, use the local toolchain, preserve the unrelated flag, and give read-only mode precedence. Assert effective settings rather than an incidental count/order of queries. - Exercise statuses 0, 1, 2, and 3 on a representative operation, stdout/stderr forwarding, and a nonzero legacy-publication result. Do not multiply all module/operation combinations by all exit statuses. - Exercise malformed wrapper invocations and confirm no Go command ran. Leave invalid semantic-version and upstream-flag classification to ReleaseKit. Use no credential file, authenticated request, or external service. Keep existing domain package and maintained-consumer tests unchanged. Run the focused scripts suite during development as needed; Stage 5 owns final complete validation. **Stage result:** local tests protect CommandKit routing and invocation behavior; upstream tests own publication, credential handling, and hosted reconciliation. ## Stage 4: Align current documentation with the implemented workflow **Completed.** The release procedure, project-specific policy, and developer guidance describe the new launcher, checks, delivery, and recovery workflow. Update `docs/release.md`, the CommandKit-specific scope in `docs/policy/release.md`, and release-tooling guidance in `docs/development.md`. Keep adopted general policy text and provenance intact. Change current owners with the implementation; the feature roadmap supplies the design rationale. Document these concrete invocation examples, using placeholders rather than selecting a new release version: - Publish: `./scripts/release core vX.Y.Z`, with corresponding integration selectors. Keep the existing core-then-ckconfig-then-ckprompt order. - Diagnose: `./scripts/releasekit core check vX.Y.Z`; optional, never a required step before publication repeats validation. - Deliver: `./scripts/releasekit core deliver vX.Y.Z`, or either integration selector with its own semantic version. The profile derives the full tag. - Discover/reconcile: `./scripts/releasekit publication-attempts` and `./scripts/releasekit reconcile --record /absolute/path/to/attempt.json`. - Inspect the tool: `./scripts/releasekit version`. Include the clean tagged-checkout prerequisite for delivery, later retries from the original tagged source, the page-only body and module-specific full tags, environment-token precedence over the repository-relative token file, and retention of the ignored credential directory. Explain that successful source publication completes the source release, while hosted delivery has a separate outcome and URL. Credentials and Gitea availability do not gate publication. Refer to upstream delivery and recovery contracts rather than duplicating an HTTP implementation or a second status model. Remove native executable-storage commands and the retired `COMMANDKIT_RELEASE_INTEGRATION` exercise instructions. Explain that selected upstream file checks inspect committed inputs, cover modules independently, and do not replace domain review. Document future licensing footer URLs using `https://forge.maximumdirect.net/base-layer/commandkit/src/tag/FULL_TAG/` plus the correct repository-relative module file path. Keep existing published notes and tags unchanged; add no speculative release note or historical backfill. No CI workflow, provider call, runtime API, or attribution update is required. Search affected documentation for stale `.releasekit/bin/` and retired test references. Keep the credential-path references. Where baseline documentation mentions a deleted source file, use its inline path and baseline revision instead of leaving a broken local hyperlink. Keep stage tracking in this plan, not in the feature roadmap. **Stage result:** the maintainer runbook describes the actual wrapper commands, selected checks, separate outcomes, and recovery without obsolete scaffolding. ## Stage 5: Verify the final source and record validation boundaries **Completed.** With Go 1.26.8 and local toolchain selection, shell syntax, executable modes, JSON parsing, ignored/untracked token path, and whitespace checks passed. The real helper reported ReleaseKit v0.8.0 under foreign target settings and an inherited workspace; a disposable copy with missing tool sums failed under inherited `-mod=mod` without changing its sums. The staged tree was archived and committed in a disposable repository. The pinned ReleaseKit core check passed, including the root suite and selected committed-file checks. Both integration checks passed using the documented temporary workspace shim, including their current-source suites and selected file checks. Independent workspace-off tests for both integrations failed on missing core symbols including `CompletionHint` and `Explanation`, consistent with the baseline dependency-version mismatch. The shimmed checks do not prove published dependency alignment or integration release readiness. Use Go 1.26.8 on `PATH`, `GOTOOLCHAIN=local`, and prepared dependency caches. Reuse checks for unchanged final content instead of repeating them for each stage. Preserve the existing ignored `go.work` (currently declaring Go 1.26.10); use an external temporary Go 1.26.8 workspace when testing current source. Perform the following one-time migration checks; add no persistent validation script or new recurring publication gate: 1. Check shell syntax and executable modes for both scripts, parse the profiles, review documentation links/fragments, and run `git diff --check`. Verify only the intended files changed and the token remains ignored and untracked. 2. Exercise the real helper's `version` with foreign `GOOS`/`GOARCH`, an inherited workspace, and an unrelated effective Go flag. Confirm ReleaseKit v0.8.0 runs natively without changing module files. In a disposable module copy, remove required tool checksum metadata and use inherited `-mod=mod`; confirm the helper fails instead of repairing metadata. Do not alter the real sum files. 3. Review and stage only the intended sprint changes, including new files, for verification. Obtain the staged tree with `git write-tree` and archive that tree into a disposable directory; do not archive `HEAD` and accidentally test the old implementation. Initialize and commit that snapshot in a new local Git repository on `main`, with no copied tag refs or publication remote. This verifies intended content before committing it in CommandKit. Do not include ignored files, unrelated staged work, or real credentials. Existing notes can label these local-only diagnostics: core `v0.3.0`, integrations `v0.1.0`. No tag is created or reused in an authoritative repository. 4. Build the pinned ReleaseKit executable once into temporary storage, using the selected root module and read-only metadata. Run ordinary upstream `check` on the snapshot's core profile with workspace discovery off. This runs the root suite, including the new scripts tests, and all selected file checks. Temporary native storage here is diagnostic setup, not launcher code. 5. For the integrations, create an external temporary workspace containing all three snapshot modules. ReleaseKit v0.8.0 intentionally forces `GOWORK=off` for its child suites, so exporting a workspace or alternate module-file flag on the outer command cannot redirect those suites. For this diagnostic only, put a temporary `go` shim on the executable's `PATH`: forward all invocations to the absolute real Go binary, but for `go test` set `GOWORK` to that temporary workspace before executing the real command. Do not skip tests or alter their remaining arguments. Run upstream `check` for both integration profiles. Real package suites then exercise current source; upstream file checks still inspect unchanged committed inputs, including ordinary `go.mod` files. This procedure was verified during planning and needs no committed helper. 6. Record workspace-backed integration results separately from independent release-readiness evidence. The baseline standalone failures listed above must remain visible. If needed, compare workspace-off tests with baseline to distinguish new failures. Do not claim the shimmed diagnostic proves that published dependencies are aligned, and do not add the shim to ordinary `check`, `publish`, or the default test suite. Dependency/version alignment and preparation of real integration candidates remain separate release work. Planning evidence: the four generic file checks passed for the baseline core; all five selected file checks passed for both baseline integration snapshots using their real workspace-backed suites. The corresponding workspace-off integration suites failed with the known missing-core-symbol errors. Repeat the relevant diagnostics for the final sprint content, not merely the baseline. Investigate new failures without turning the baseline limitation into a false green result or disabling an agreed validator. Do not invoke `publish`, `deliver`, or `ci-deliver` against a real destination for verification. Configuration and forwarding are the local responsibility; ReleaseKit owns authenticated preflight, hosted writes, and content matching. **Stage result:** the changed infrastructure, real current-source suites, and selected committed-file checks are verified, with independent integration release-readiness limits accurately reported. ## Stage 6: Review, document completion, and commit **Completed.** The implementation was reviewed against the feature roadmap; validation and the integration dependency limitation are recorded above. The roadmap documents remain available for later release preparation. Review the complete diff against the feature roadmap. Confirm that all three profiles have the selected options and destination; one shared launcher owns module routing; the old publication exercise and unused helpers are gone; domain tests remain; documentation uses the new invocation and separate outcomes; and `/.releasekit/` still protects the local token directory. Record stages completed and final validation results in this document, including the dependency-version limitation. Do not retire these roadmap documents during this sprint: retain the target and completion evidence for review, then apply the normal roadmap lifecycle during later release preparation. No new release version or source/hosted publication is required to finish this implementation. Follow the local commit policy, review staged changes, and commit the intended implementation units with short plain-English messages. Report the commit IDs, remaining work, and verification limits. Preserve unrelated changes and private files; do not force a clean worktree by deleting or including them. With only the planned sprint changes present, the resulting tracked worktree should be clean. Intermediate failures do not justify leaving the final selected scope partially implemented. **Stage result:** a reviewable, committed implementation of the selected target with accurate validation evidence and no production publication.